Astraioz
Astraioz

Reputation: 65

Maximum FPS is exceeded

I am trying to calculate the FPS of my program. Although the given maximum FPS is beeing overrun and I cannot explain why. I tested it with MAX_FPS set to 60 and I always get about 63 displayed.

{...}
int frameCount = 0;
long time = System.currentTimeMillis();
while(running) {
    try {
        {...} // do something
        Thread.sleep(1000 / MAX_FPS);
        frameCount++;
        if (System.currentTimeMillis() - time >= 1000) {
            {...} // display FPS w/ frameCount
            frameCount = 0;
            time = System.currentTimeMillis();
        }
    } catch (InterruptedException ex) {
        System.err.println(ex.toString());
    }
}
{...}

Upvotes: 2

Views: 187

Answers (3)

Andy Turner
Andy Turner

Reputation: 140299

The problem that you have with reaching the target FPS, base on NESPowerGlove's answer, is that you've got to pick an integer value to call Thread.sleep() for - if you pick 16, you'll get about 63fps; if you pick 17, you'll get around 58fps.

There are two solutions that I can think of to this:

  1. Implement some sort of negative feedback loop to control the FPS. For example, I found that the following PI (proportional + integral) controller kept the FPS around 60:

    final long sleepTime = (long) (1000.0 / TARGET_FPS);
    
    System.out.println("Sleep time is " + sleepTime);
    long time = System.nanoTime();
    long delta = 0;
    double cumulativeFpsError = 0.0;
    for (int frameCount = 0; true; ++frameCount) {
      Thread.sleep((long) (sleepTime + delta));
      long elapsed = System.nanoTime() - time;
      if (elapsed >= 1_000_000_000) {
        double fps = (double) frameCount / elapsed * 1e9;
        System.out.println(fps);
    
        cumulativeFpsError += (fps - 60);
        delta += (fps - TARGET_FPS) * 0.55 + 0.14 * cumulativeFpsError;
        frameCount = 0;
        time += elapsed;
      }
    }
    

The values of 0.55 and 0.14 were found by trial-and-error. Better values are probably available (but at least it appears to be roughly stable). fps output:

61.08042479827653
61.817816897275485
58.42717726642977
62.0654826347193
58.43759367657694
62.07263954479402
58.444556146850026
62.05489635777375
58.4438970272065
62.05784933619571
58.45590905841833
62.069491426232766
58.44381852435569
62.07438904528996
...

Actually, the integral term doesn't do much at all - presumably because the sleep value can only vary in steps of size 1.

  1. Use a different method to sleep for a more precise amount of time. For example, How to suspend a java thread for a small period of time, like 100 nanoseconds? suggests some alternatives, like polling System.nanoTime() until some deadline is exceeded. e.g.

    long time = System.nanoTime();
    for (int frameCount = 0; true; ++frameCount) {
      long end = System.nanoTime() + (long) (1_000_000_000.0 / TARGET_FPS);
      while (System.nanoTime() < end);
      long elapsed = System.nanoTime() - time;
      if (elapsed >= 1_000_000_000) {
        double fps = (double) frameCount / elapsed * 1e9;
        System.out.println(fps);
    
        frameCount = 0;
        time = System.nanoTime();
      }
    }
    

This seems to work better, with the FPS hovering just under 60:

58.99961555850502
59.99966304189236
59.99942898543434
59.99968068169941
59.99968770162551
59.99919595077507
59.99945862488483
59.999679241714766
59.99753134157542
59.99963898217224
59.999265728986
...

The disadvantage of this might be that while loop is going to be quite hot.

Of course, you could do some sort of hybrid approach, using Thread.sleep for most of the wait, then using the hot while loop to get a more precise delay.

Upvotes: 1

Andy Turner
Andy Turner

Reputation: 140299

You might want to use System.nanoTime() instead. Not for any extra precision, but because it's a more correct way of measuring elapsed time. See Kevin Bourillion's answer to How do I measure time elapsed in Java?.

Upvotes: 2

NESPowerGlove
NESPowerGlove

Reputation: 5496

1000 / 60 is 16 in integer math, and 1000 / 16 is roughly 62.5. Assuming the thread does sleep for close to 16 milliseconds (maybe a little less), and if your loop doesn't take that long to execute an iteration, and if the second to last frame comes through at 999 milliseconds allowing another update to sneak in, then it makes sense that the loop could achieve 63 iterations a second often.

Upvotes: 2

Related Questions