当我编写动画和小游戏时,我已经知道了Thread.sleep(n);
的难以置信的重要性,我依靠这种方法来告诉操作系统,当我的应用程序不需要任何CPU时,并使用这种方法使我的程序以可预测的速度进行。
我的问题是,JRE在不同的操作系统上使用不同的方法来实现此功能。在基于UNIX的(或受其影响的) OS上:es,如Ubuntu和OS,底层的JRE实现使用一个功能良好且精确的系统来将CPU时间分配给不同的应用程序,从而使我的2D游戏流畅且无延迟。但是,在Windows 7和更早的Microsoft系统上,CPU时间分布的工作方式似乎有所不同,您通常会在给定的睡眠时间后找回CPU时间,距离目标睡眠时间大约1-2毫秒。然而,你偶尔会得到额外的10-20毫秒的睡眠时间。当这种情况发生时,这会导致我的游戏每隔几秒钟就会延迟一次。我注意到这个问题存在于我在Windows上尝试过的大多数Java游戏中,“我的世界”就是一个明显的例子。
现在,我一直在互联网上寻找解决这个问题的方法。我见过很多人只使用Thread.yield();
而不使用Thread.sleep(n);
,无论你的游戏实际需要多少CPU,它都会以当前使用的CPU核心得到满载为代价来完美地工作。这并不适合在笔记本电脑或高能耗工作站上玩游戏,而且在Mac和Linux系统上也是不必要的权衡。
再往下看,我发现了一种常用的纠正睡眠时间不一致的方法,称为“自旋睡眠”,在这种方法中,你一次只能安排1ms的睡眠,并使用System.nanoTime();
方法检查一致性,即使在微软的系统上,这种方法也非常准确。这对正常的1-2ms的睡眠不一致有帮助,但对偶尔的+10-20ms的睡眠不一致没有帮助,因为这通常会导致花费的时间超过我的循环的一个周期。
经过大量的寻找,我找到了Andy Malakov的这篇晦涩的文章,这篇文章对改进我的循环非常有帮助:http://andy-malakov.blogspot.com/2010/06/alternative-to-threadsleep.html
基于他的文章,我写了这个睡眠方法:
// Variables for calculating optimal sleep time. In nanoseconds (1s = 10^-9ms).
private long timeBefore = 0L;
private long timeSleepEnd, timeLeft;
// The estimated game update rate.
private double timeUpdateRate;
// The time one game loop cycle should take in order to reach the max FPS.
private long timeLoop;
private void sleep() throws InterruptedException {
// Skip first game loop cycle.
if (timeBefore != 0L) {
// Calculate optimal game loop sleep time.
timeLeft = timeLoop - (System.nanoTime() - timeBefore);
// If all necessary calculations took LESS time than given by the sleepTimeBuffer. Max update rate was reached.
if (timeLeft > 0 && isUpdateRateLimited) {
// Determine when to stop sleeping.
timeSleepEnd = System.nanoTime() + timeLeft;
// Sleep, yield or keep the thread busy until there is not time left to sleep.
do {
if (timeLeft > SLEEP_PRECISION) {
Thread.sleep(1); // Sleep for approximately 1 millisecond.
}
else if (timeLeft > SPIN_YIELD_PRECISION) {
Thread.yield(); // Yield the thread.
}
if (Thread.interrupted()) {
throw new InterruptedException();
}
timeLeft = timeSleepEnd - System.nanoTime();
}
while (timeLeft > 0);
}
// Save the calculated update rate.
timeUpdateRate = 1000000000D / (double) (System.nanoTime() - timeBefore);
}
// Starting point for time measurement.
timeBefore = System.nanoTime();
}
为了在我的Windows7机器上获得最佳性能,我通常将SLEEP_PRECISION
设置为大约2ms,将SPIN_YIELD_PRECISION
设置为大约10000 ns。
经过大量的艰苦工作,这是我能想到的最好的办法了。所以,由于我仍然关心提高这种睡眠方法的准确性,而且我仍然对性能不满意,所以我想呼吁所有java游戏黑客和动画师为Windows平台提供更好的解决方案的建议。我可以在Windows上使用特定于平台的方式来使其更好吗?我不关心在我的应用程序中有一些特定于平台的代码,只要大多数代码是独立于操作系统的。
我还想知道是否有人知道微软和甲骨文正在更好地实现Thread.sleep(n);
方法,或者甲骨文未来计划如何改善他们的环境,作为需要高计时精度的应用程序的基础,例如音乐软件和游戏?
感谢大家阅读我冗长的问题/文章。我希望一些人会发现我的研究是有帮助的!
发布于 2011-03-12 00:11:15
在windows上计时是出了名的糟糕。This article是一个很好的起点。我不确定你是否关心,但也要注意,在虚拟系统上(当windows是客户操作系统时)也可能会有更严重的问题(特别是System.nanoTime)。
发布于 2011-03-11 23:16:25
Thread.Sleep说你的应用程序不需要更多时间。这意味着在最坏的情况下,您将不得不等待整个线程切片(40ms左右)。
现在,在最坏的情况下,当一个驱动程序或某件事情占用更多的时间时,你可能需要等待120ms (3*40ms),所以Thread.Sleep不是最好的方法。采取另一种方式,比如注册一个1ms的回调,并开始绘制代码非常X的回调。
(这是在windows上,我会使用MultiMedia工具来获取1ms分辨率回调)
发布于 2012-01-23 06:35:45
Thread.sleep
不准确,并且会使动画在大部分时间内抖动。
如果你完全用Thread.yield
代替它,你将得到一个稳定的FPS,没有延迟或抖动,但是CPU使用率大大增加。我很久以前就搬到了Thread.yield。
这个问题已经在Java游戏开发论坛上讨论了很多年了。
https://stackoverflow.com/questions/5274619
复制相似问题