2

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());
    }
}
{...}
Astraioz
  • 65
  • 2
  • 6
  • 2
    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. Edit: I'll just make this an answer. – NESPowerGlove Feb 09 '15 at 22:57
  • @Astraioz - surely you need `1_000_000_000` for nanoseconds? (Or use `TimeUnit` to do the conversion) – Andy Turner Feb 09 '15 at 23:13

3 Answers3

2

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.

NESPowerGlove
  • 5,496
  • 17
  • 28
  • @Astraioz - which is the answer - mine or NESPowerGlove's - you've described both as the answer to your problem :) – Andy Turner Feb 09 '15 at 23:12
  • @NESPowerGlove @AndyTurner I am sorry, I did not check right. NESPowerGlove explained my problem but did not solve it. You did not either. Yes, `System.nanoTime()` is the more correct way but does not solve my problem. – Astraioz Feb 09 '15 at 23:23
  • I need to recorrect myself again, sorry. NESPowerGlove has the right answer. And in addition I want to know if there is a way to measure it so it does not exceed the given maximum value. – Astraioz Feb 09 '15 at 23:27
  • @Astraioz it's possible to be a bit more accurate with Thread.sleep, you just have to adjust how long you sleep per frame update, I'd imagine it would be based on some math of how long one iteration sleeps on average in the given past second and how long on average a game loop iteration takes to execute. Is there a reason why you don't want to exceed a given maximum FPS value, are you updating animations and sprite positions per render instead of how much time has elapsed between renders or game loop iterations? – NESPowerGlove Feb 10 '15 at 14:33
  • @Astraioz You at least should already be taking into effect how long an average render takes for an improved sleep time, but either way trying to get a consistent FPS value through using Thread.sleep is not going to be easy as the granularity of the timer is at the mercy of the OS, and is not guaranteed in anyway (which is why, if you choose this route, should try to adapt sleeps based on actual sleep times). A better way though might be to put the logic of a game loop iteration in a java.util.Timer with a fixed delay interval. – NESPowerGlove Feb 10 '15 at 14:48
  • @NESPowerGlove No I don't use the FPS to update animations and sprite positions, otherwise the FPS would be dependant of other loops. If there is a game loop for instance, I would need to check the elapsed time for processing and rendering and then calculate how long I need to wait to process the next frame. In my case I just repaint the frame and do not calculate anything, because I simply do not need it. Updating animations and sprite positions would be a reason to not exceed the given maximum FPS vlaue, however in my case it's just for "vanity" reason. – Astraioz Feb 10 '15 at 15:00
  • @Astraioz Ok, well I would go the way of a java.util.Timer with a fixed delay interval, that timer gives you the behavior you want, of scaling time to make up for slow loop iterations and scaling time to hit a consistent update speed. – NESPowerGlove Feb 10 '15 at 15:49
2

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?.

Community
  • 1
  • 1
Andy Turner
  • 137,514
  • 11
  • 162
  • 243
  • I didn't downvote. But maybe it's because you didn't actually answer the question, which was to explain why the expected number of FPS was being exceeded. I'm just guessing. You'd have to ask the downvoter, and as I said, it wasn't me. – Dawood ibn Kareem Feb 09 '15 at 23:46
  • @DavidWallace - I added it as an answer because OP said that my comment fixed it for him. I was a bit surprised, but there you go. – Andy Turner Feb 09 '15 at 23:51
1

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.

Community
  • 1
  • 1
Andy Turner
  • 137,514
  • 11
  • 162
  • 243