我正在尝试计算我的程序的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());
}
}
{...}
答案 0 :(得分:2)
1000/60在整数数学中是16,而1000/16是大约62.5。假设线程睡眠时间接近16毫秒(可能稍微少一些),并且如果你的循环没有花那么长的时间来执行迭代,并且倒数第二帧的时间是999毫秒,允许另一个更新潜入那么循环经常可以每秒实现63次迭代。
答案 1 :(得分:2)
您可能希望改用System.nanoTime()
。不是为了任何额外的精度,而是因为它是一种更准确的测量经过时间的方法。请参阅Kevin Bourillion对How do I measure time elapsed in Java?的回答。
答案 2 :(得分:1)
根据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()
,直到超过某个截止日期。 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();
}
}
这似乎效果更好,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
循环来获得更精确的延迟。