簡體   English   中英

繁忙的等待,睡眠和准確性

[英]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的納秒被完全忽略。

因此,我仔細研究了建議並在先前發布的代碼之上測試了這三個變體:

ScheduledExecutorService

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM