超过最大 FPS
Maximum FPS is exceeded
我正在尝试计算程序的 FPS。虽然给定的最大 FPS 正在超限,但我无法解释原因。我在 MAX_FPS
设置为 60
的情况下对其进行了测试,我总是显示大约 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());
}
}
{...}
1000 / 60 在整数数学中是 16,而 1000 / 16 大约是 62.5。假设线程确实休眠了将近 16 毫秒(可能少一点),并且如果您的循环不会花费那么长时间来执行迭代,并且如果倒数第二帧在 999 毫秒时通过允许另一个更新潜入, 那么循环可以达到每秒 63 次迭代是有道理的。
您可能想改用 System.nanoTime()
。不是为了任何额外的精度,而是因为它是一种更正确的测量经过时间的方法。请参阅 Kevin Bourillion 对 How do I measure time elapsed in Java? 的回答。
根据 NESPowerGlove 的回答,您在达到目标 FPS 时遇到的问题是您必须选择一个整数值来调用 Thread.sleep()
- 如果您选择 16
,你会得到大约 63fps;如果您选择 17
,您将获得大约 58fps。
对此我能想到两种解决方案:
实施某种负反馈回路来控制 FPS。例如,我发现下面的PI(比例+积分)控制器保持FPS在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;
}
}
0.55
和0.14
的值是通过反复试验找到的。可能有更好的值(但至少它看起来大致稳定)。 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
...
实际上,积分项根本没有太大作用 - 大概是因为睡眠值只能以大小 1 的步长变化。
使用不同的方法来获得更精确的睡眠时间。例如,How to suspend a java thread for a small period of time, like 100 nanoseconds? 提出了一些替代方案,例如轮询 System.nanoTime()
直到超过某个截止日期。例如
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();
}
}
这似乎效果更好,FPS 徘徊在 60 以下:
58.99961555850502
59.99966304189236
59.99942898543434
59.99968068169941
59.99968770162551
59.99919595077507
59.99945862488483
59.999679241714766
59.99753134157542
59.99963898217224
59.999265728986
...
这样做的缺点可能是 while
循环会很热。
当然,您可以采用某种混合方法,对大部分等待使用 Thread.sleep
,然后使用热 while
循环来获得更精确的延迟。
我正在尝试计算程序的 FPS。虽然给定的最大 FPS 正在超限,但我无法解释原因。我在 MAX_FPS
设置为 60
的情况下对其进行了测试,我总是显示大约 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());
}
}
{...}
1000 / 60 在整数数学中是 16,而 1000 / 16 大约是 62.5。假设线程确实休眠了将近 16 毫秒(可能少一点),并且如果您的循环不会花费那么长时间来执行迭代,并且如果倒数第二帧在 999 毫秒时通过允许另一个更新潜入, 那么循环可以达到每秒 63 次迭代是有道理的。
您可能想改用 System.nanoTime()
。不是为了任何额外的精度,而是因为它是一种更正确的测量经过时间的方法。请参阅 Kevin Bourillion 对 How do I measure time elapsed in Java? 的回答。
根据 NESPowerGlove 的回答,您在达到目标 FPS 时遇到的问题是您必须选择一个整数值来调用 Thread.sleep()
- 如果您选择 16
,你会得到大约 63fps;如果您选择 17
,您将获得大约 58fps。
对此我能想到两种解决方案:
实施某种负反馈回路来控制 FPS。例如,我发现下面的PI(比例+积分)控制器保持FPS在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; } }
0.55
和0.14
的值是通过反复试验找到的。可能有更好的值(但至少它看起来大致稳定)。 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
...
实际上,积分项根本没有太大作用 - 大概是因为睡眠值只能以大小 1 的步长变化。
使用不同的方法来获得更精确的睡眠时间。例如,How to suspend a java thread for a small period of time, like 100 nanoseconds? 提出了一些替代方案,例如轮询
System.nanoTime()
直到超过某个截止日期。例如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(); } }
这似乎效果更好,FPS 徘徊在 60 以下:
58.99961555850502
59.99966304189236
59.99942898543434
59.99968068169941
59.99968770162551
59.99919595077507
59.99945862488483
59.999679241714766
59.99753134157542
59.99963898217224
59.999265728986
...
这样做的缺点可能是 while
循环会很热。
当然,您可以采用某种混合方法,对大部分等待使用 Thread.sleep
,然后使用热 while
循环来获得更精确的延迟。