在多个帧上分发长时间运行任务的最佳方法是什么?

时间:2018-02-06 11:37:07

标签: javascript

我有多个长时间运行的任务,长度大于10毫秒,这会影响浏览器的响应能力。最糟糕的一些,例如从文件加载和解析3D模型,已经卸载到Web Workers,因此它们不会影响渲染循环。

然而,有些任务不容易移植到Workers,因此必须分布在主线程中的多个框架上。我不是一次性执行1秒任务,而是将其拆分为~5ms包,以使浏览器有机会在其间执行其他事件(鼠标移动,requestAnimationFrame,...)。

生成器函数与setTimeout结合使用似乎是最简单的方法。我已经在一起攻击了一些可以完成工作的东西,但我想知道是否有更好/更清洁的方法可以解决这个问题。

下面的代码计算了Math.random()的1亿次调用​​的平均值。 第一个版本一次计算平均值,但停止浏览器约1.3秒。 第二个版本滥用生成器函数以在每500万个点之后产生,从而使浏览器有机会在其间执行其他事件(鼠标移动)。通过setTimout循环重复调用生成器函数,直到它处理完所有1亿个样本。

<html>
<head></head>
<body>

<script>

    let samples = 100 * 1000 * 1000;

    { // run complete task at once, possibly stalling the browser
        function run(){
            let start = performance.now();

            let sum = 0.0;
            for(let i = 0; i < samples; i++){
                sum = sum + Math.random();
            }

            let mean = sum / samples;

            let duration = performance.now() - start;

            console.log(`single-run: duration: ${duration}`);
            console.log(`single-run: sum: ${sum}`);
            console.log(`single-run: mean: ${mean}`);
        }
        run();
    }


    { // run in smaller packages to keep browser responsive

        // move mouse to check if this callback is executed in between
        document.body.addEventListener("mousemove", () => {
            console.log("mouse moved");
        });

        function * distributedRun(){
            let start = performance.now();

            let packageSize = 5 * 1000 * 1000;
            let sum = 0.0;
            for(let i = 0; i < samples; i++){
                sum = sum + Math.random();

                if((i % packageSize) === 0){
                    yield sum;
                }
            }

            let mean = sum / samples;

            let duration = performance.now() - start;


            console.log(`distributed-run: duration: ${duration}`);
            console.log(`distributed-run: sum: ${sum}`);
            console.log(`distributed-run: mean: ${mean}`);

            yield sum;
        }

        let generatorInstance = distributedRun();
        function loop(){
            let result = generatorInstance.next();
            console.log(`distributed-run intermediate result: ${result.value}`);

            if(!result.done){
                setTimeout(loop, 0);
            }
        }
        loop();
    }

</script>

</body>
</html>

ES2018有异步迭代器,这听起来像我正在寻找的那样,但我不确定它们是否真的意味着这类问题。像这样使用它仍然会拖延浏览器:

for await (const result of distributedRun()) {
    ...
}

(在这里和那里尝试了一些异步,并且在runDistributed()函数中,但是我仍然在学习await / async的详细信息)

1 个答案:

答案 0 :(得分:0)

这是您的代码的稍作修改的版本。如果根据您的计算复杂度和可以允许的延迟量来调整chunk,它应该可以正常工作。

let samples = 100 * 1000 * 1000;
let chunk = 100000;

async function run() {
    let sum = 0.0;
    for(let i=0; i<samples; i++) {
        sum += Math.random();

        if (i % chunk === 0) {
            console.log("finished chunk")
            // wait for the next tick
            await new Promise(res => setTimeout(res, 0));
        }
    }

    let mean = sum / samples;
    console.log("finished computation", mean);
}

setTimeout(run, 0);