Estoy intentando calcular el FPS de mi programa. Aunque el FPS máximo dado está siendo superado y no puedo explicar por qué. Lo probé con MAX_FPS configurado en 60 y siempre aparece alrededor de 63.

{...}
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());
    }
}
{...}
2
Astraioz 10 feb. 2015 a las 01:52

3 respuestas

La mejor respuesta

El problema que tiene para alcanzar el FPS objetivo, basado en la respuesta de NESPowerGlove, es que debe elegir un valor entero para llamar a Thread.sleep(); si elige 16, obtendrá aproximadamente 63 fps; si elige 17, obtendrá alrededor de 58 fps.

Hay dos soluciones en las que puedo pensar para esto:

  1. Implemente algún tipo de circuito de retroalimentación negativa para controlar el FPS. Por ejemplo, encontré que el siguiente controlador PI (proporcional + integral) mantenía el FPS alrededor 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;
      }
    }
    

Los valores de 0.55 y 0.14 se encontraron mediante prueba y error. Probablemente haya mejores valores disponibles (pero al menos parece ser más o menos estable). salida de fps:

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

En realidad, el término integral no hace mucho en absoluto, presumiblemente porque el valor de sueño solo puede variar en pasos de tamaño 1.

  1. Utilice un método diferente para dormir durante un período de tiempo más preciso. Por ejemplo, ¿Cómo suspender un hilo de Java por un pequeño período de tiempo, como 100 nanosegundos? sugiere algunas alternativas, como sondear System.nanoTime() hasta que se exceda una fecha límite. p.ej.

    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();
      }
    }
    

Esto parece funcionar mejor, con el FPS rondando un poco menos de 60:

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

La desventaja de esto podría ser que el bucle while va a estar bastante caliente.

Por supuesto, podría hacer algún tipo de enfoque híbrido, usando Thread.sleep durante la mayor parte de la espera, luego usando el ciclo while caliente para obtener un retraso más preciso.

1
Community 23 may. 2017 a las 11:50

Es posible que desee utilizar System.nanoTime() en su lugar. No por una precisión adicional, sino porque es una forma más correcta de medir el tiempo transcurrido. Consulte la respuesta de Kevin Bourillion a ¿Cómo mido el tiempo transcurrido en Java?.

2
Community 23 may. 2017 a las 11:56

1000/60 es 16 en matemáticas enteras y 1000/16 es aproximadamente 62,5. Suponiendo que el hilo duerme durante cerca de 16 milisegundos (tal vez un poco menos), y si su bucle no tarda tanto en ejecutar una iteración, y si el penúltimo fotograma llega a 999 milisegundos, lo que permite que se cuele otra actualización , entonces tiene sentido que el ciclo pueda lograr 63 iteraciones por segundo a menudo.

2
NESPowerGlove 9 feb. 2015 a las 22:59