[英]Busy wait, sleep and accuracy
我現在正在嘗試編寫簡單的渲染器,該渲染器將以60Hz的頻率調用render方法,並休眠額外的時間以節省其他人的CPU周期。
我在跟隨時遇到了一個簡單的問題
while(m_Running){
//start meassuring whole frame time
t0 = System.nanoTime();
render();
//meassure time spent rendering
t1 = System.nanoTime();
if(t1<nextRedraw){
try{
diff = nextRedraw-t1;
ms = diff/1000000;
ns = (int) (diff-(ms*1000000));
Thread.sleep(ms, ns);
}catch(InterruptedException e){
Logger.logWarning("Renderer interrupted!",e);
}
//while(System.nanoTime()<nextRedraw);//busy wait alternative
}
//meassure time spent sleeping
t2 = System.nanoTime();
nextRedraw = t2+m_RedrawTimeout;
long frameTime = t2-t0;
long renderTime = t1-t0;
long sleepTime = t2-t1;
long fps = 1000000000/frameTime;
}
可以正常運行,但遠沒有達到預期的60fps,而是在數值附近跳躍
FPS: 63 Frame: 15,7ms Render: 2,0ms Sleep: 13,7ms
FPS: 64 Frame: 15,5ms Render: 2,0ms Sleep: 13,5ms
FPS: 63 Frame: 15,7ms Render: 2,1ms Sleep: 13,5ms
FPS: 59 Frame: 16,7ms Render: 2,8ms Sleep: 14,0ms
FPS: 64 Frame: 15,5ms Render: 2,2ms Sleep: 13,3ms
當我嘗試使用繁忙等待時,結果會更加一致並且更接近我想要的fps目標。
FPS: 60 Frame: 16,4ms Render: 2,0ms Sleep: 14,5ms
FPS: 60 Frame: 16,4ms Render: 2,0ms Sleep: 14,4ms
FPS: 60 Frame: 16,4ms Render: 2,0ms Sleep: 14,4ms
FPS: 61 Frame: 16,3ms Render: 2,4ms Sleep: 13,8ms
FPS: 61 Frame: 16,3ms Render: 2,1ms Sleep: 14,2ms
FPS: 60 Frame: 16,4ms Render: 2,0ms Sleep: 14,4ms
我想避免這種情況,因為這是可以理解的CPU缺陷。 我有個想法,就是睡得比必要的少一些,並精確地遍歷剩余時間,但這似乎有些笨拙。
我的問題是,是否忙於通過編譯器以某種方式進行優化,或者是否有其他方法可以達到類似的時序?
任何幫助,將不勝感激。
親切的問候,
Vojtěch
注意:我使用System.nanoTime(); 為了使事情更准確,它沒有幫助,但我不知道它的任何性能缺陷
注意:我知道睡眠很不准確,但是我沒有找到其他選擇
我猜想,時間上的細微差異可能總計更大,您可以通過使睡眠取決於總時間來輕松避免這種情況。
就像是
long basetime = System.nanoTime();
for (int i=0; m_Running; ++i) {
...
nextRedraw = baseTime + i * m_RedrawTimeout;
}
如果當前睡眠時間太短,則應該使隨后的睡眠時間縮短,反之亦然,因此您獲得的FPS最多每秒變化幾毫秒(即,<1%)。
也許沒有問題,只是您要測量的是單個幀花費的時間。 這個數字略有不同,關於FPS的比率並沒有多說。
AFAIK Thread.sleep
的納秒被完全忽略。
因此,我仔細研究了建議並在先前發布的代碼之上測試了這三個變體:
private final ScheduledExecutorService scheduler
= Executors.newScheduledThreadPool(1);
//handle for killing the process
private ScheduledFuture<?> handle = null;
//meassuring actual time between redraws
private long lastRenderStart = 0;
//start will fire off new scheduler at desired 60Hz
@Override
public synchronized void start() {
handle = scheduler.scheduleAtFixedRate(this,
100000000, m_RedrawTimeout, TimeUnit.NANOSECONDS);
}
public void kill(){
if(handle!=null){
handle.cancel(true);
}
}
@Override
public void run(){
//meassured render routine
long t0 = System.nanoTime();
render();
long t1 = System.nanoTime();
System.out.format("render time %.1fms\t\tframe time %.1fms\n",
((t1-t0)/1000000.0d),
((t0-lastRenderStart)/1000000.0d));
lastRenderStart = t0;
}
這使代碼以某種方式更簡單,並且AFAIK不會引入任何開銷,但是精度仍然不是我想要的關鍵
render time 1,4ms frame time 17,0ms
render time 1,4ms frame time 16,0ms
render time 1,7ms frame time 17,0ms
render time 1,3ms frame time 17,0ms
render time 1,8ms frame time 16,0ms
render time 14,8ms frame time 16,9ms
render time 2,0ms frame time 17,0ms
long nextRedraw,baseTime = System.nanoTime();
m_Running=true;
for (long i=0; m_Running; ++i) {
long t0 = System.nanoTime();
render();
long t1 = System.nanoTime();
nextRedraw = baseTime + i * m_RedrawTimeout;
long now = System.nanoTime();
if(now<nextRedraw){
try{
Thread.sleep((nextRedraw-now)/1000000);
}catch(InterruptedException e){
Logger.logWarning("Renderer interrupted!",e);
}
}
long t2 = System.nanoTime();
long frameTime = t2-t0;
long renderTime = t1-t0;
long sleepTime = t2-t1;
long fps = 1000000000/frameTime;
System.out.format("FPS: %d\tFrame: %3.1fms\t"
+"Render: %3.1fms\tSleep: %3.1fms\n",
fps, frameTime/1000000.0, renderTime/1000000.0, sleepTime/1000000.0);
}
這種方法通常使幀時間保持在16.67 ms左右,並且看起來比最初發布的代碼更優雅,但是峰值仍然短了1 ms(出於某種原因,可能是在系統調度程序中四舍五入嗎?)
FPS: 60 Frame: 16,6ms Render: 1,7ms Sleep: 14,9ms
FPS: 60 Frame: 16,6ms Render: 1,8ms Sleep: 14,8ms
FPS: 63 Frame: 15,7ms Render: 2,3ms Sleep: 13,4ms
FPS: 59 Frame: 16,7ms Render: 1,8ms Sleep: 14,8ms
FPS: 63 Frame: 15,6ms Render: 2,0ms Sleep: 13,7ms
FPS: 60 Frame: 16,7ms Render: 1,9ms Sleep: 14,7ms
FPS: 60 Frame: 16,6ms Render: 1,9ms Sleep: 14,7ms
FPS: 59 Frame: 16,8ms Render: 1,8ms Sleep: 15,0ms
FPS: 64 Frame: 15,6ms Render: 1,9ms Sleep: 13,7ms
FPS: 60 Frame: 16,6ms Render: 1,9ms Sleep: 14,7ms
FPS: 60 Frame: 16,6ms Render: 1,8ms Sleep: 14,8ms
在這種情況下,我嘗試將繁忙的等待段添加到循環中,以使線程休眠以達到精確幀速率后等待剩余時間。
long nextRedraw,baseTime = System.nanoTime();
m_Running=true;
for (long i=0; m_Running; ++i) {
long t0 = System.nanoTime();
render();
long t1 = System.nanoTime();
nextRedraw = baseTime + i * m_RedrawTimeout;
if(t1<nextRedraw){
//if sleepy time is bigger than 1ms, use Thread.sleep
if(nextRedraw-t1>1000000){
try{
Thread.sleep(((nextRedraw-t1-1000000)/1000000));
}catch(InterruptedException e){
Logger.logWarning("Renderer interrupted!",e);
}
}
t2 = System.nanoTime();
//do busy wait on last ms or so
while(System.nanoTime()<nextRedraw);
}
long t3 = System.nanoTime();
long frameTime = t3-t0;
long renderTime = t1-t0;
long sleepTime = t2-t1;
long busyWaitTime = t3-t2;
long fps = 1000000000/frameTime;
System.out.format("FPS: %d\tFrame: %3.1fms\t"
+ "Render: %3.1fms\tSleep: %3.1fms\tBusyW: %3.1fms\n",
fps, frameTime/1000000.0, renderTime/1000000.0,
sleepTime/1000000.0,busyWaitTime/1000000.0);
}
看起來有點混亂,對此感到抱歉,但是以大約2ms的CPU等待時間交換,我在60FPS上獲得了相當穩定的時間。
FPS: 60 Frame: 16,5ms Render: 1,5ms Sleep: 13,8ms BusyW: 1,2ms
FPS: 60 Frame: 16,5ms Render: 1,4ms Sleep: 13,2ms BusyW: 1,8ms
FPS: 60 Frame: 16,4ms Render: 1,6ms Sleep: 12,4ms BusyW: 2,5ms
FPS: 60 Frame: 16,5ms Render: 1,2ms Sleep: 13,1ms BusyW: 2,1ms
FPS: 60 Frame: 16,5ms Render: 1,4ms Sleep: 13,3ms BusyW: 1,8ms
FPS: 60 Frame: 16,4ms Render: 1,4ms Sleep: 12,5ms BusyW: 2,5ms
FPS: 60 Frame: 16,5ms Render: 1,2ms Sleep: 13,1ms BusyW: 2,2ms
FPS: 60 Frame: 16,5ms Render: 1,3ms Sleep: 13,4ms BusyW: 1,8ms
FPS: 60 Frame: 16,4ms Render: 1,5ms Sleep: 12,5ms BusyW: 2,5ms
因此,感謝大家的建議,希望這對某人有所幫助:)
親切的問候,
Vojtěch
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.