忙碌的等待,睡眠和准确性

时间:2014-12-04 22:43:02

标签: java optimization

我现在正在尝试编写一个简单的渲染器,它将以60Hz的频率调用渲染方法,并为其他人节省额外的时间来节省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();为了使事情更准确,它没有帮助,但我不知道它有任何性能缺陷

注意:我知道睡眠非常不准确,但我没有找到任何其他选择

2 个答案:

答案 0 :(得分:1)

我想,时间的微小差异可能会增加更多的数量,你可以通过让睡眠取决于总时间来轻松避免它。

这样的东西
long basetime = System.nanoTime();
for (int i=0; m_Running; ++i) {
    ...
    nextRedraw = baseTime + i * m_RedrawTimeout;
}

如果当前的一个太短而反之亦然,这应该使后续睡眠更短,所以你得到的FPS每秒最多只变化几毫秒(即<1%)。


也许没有问题,只是你测量的是单帧占用多长时间。这个数字有点不同,并没有多说FPS费率。

AFAIK Thread.sleep中的纳秒被完全忽略。

答案 1 :(得分:0)

因此,我在以前发布的代码之上完成了建议并测试了这三种变体:

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左右,并且看起来比最初发布的代码更优雅,但仍有1ms的峰值(无论出于何种原因,可能在系统调度程序中舍入?)

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);
    }

看起来有点凌乱,对不起,但是在交换大约等待2毫秒的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