JavaScript 性能长时间运行的任务

标签 javascript performance recursion

前几天我注意到这里有一个问题( Reducing Javascript CPU Usage ),我很感兴趣。

本质上,这个人想要逐个字符地加密一些文件。显然,一次性完成所有这些操作将会锁定浏览器。

他的第一个想法是一次以大约 1kb 的字符串 block 的形式进行处理,然后暂停 X 毫秒,这样用户就可以在处理之间保持与页面的交互。他还考虑过使用 webWorkers(最好的主意),但它显然不是跨浏览器的。

现在我真的不想解释为什么这在 javascript 中可能不是一个好主意。但我想看看是否能想出一个解决方案。

我记得看过 Douglas Crockford 的视频 at js conf 。该视频与 Node.js 和事件循环相关。但我记得他谈到将长时间运行的函数分解为单独的 block ,因此新调用的函数会到达事件循环的末尾。而不是用长时间运行的任务阻塞事件循环,从而防止其他事情发生。

我知道这是一个值得我研究的解决方案。作为一名前端开发人员,我从未真正经历过 JS 中运行时间极长的任务,并且热衷于了解如何分解它们以及它们如何执行。

我决定尝试一个递归函数,它从 0 毫秒的 setTimeout 内部调用自身。我认为这将为事件循环中想要在运行时发生的任何其他事情提供中断。但我也认为,当没有其他事情发生时,你将获得最大的计算量。

这是我的想法。

(我要为代码道歉。我正在控制台中进行实验,所以这又快又脏。)

function test(i, ar, callback, start){
    if ( ar === undefined ){
        var ar = [],
        start = new Date;
    };
    if ( ar.length < i ){
        ar.push( i - ( i - ar.length )  );
        setTimeout(function(){
            test( i, ar, callback, start);
        },0);
    }
    else {
        callback(ar, start);
    };
}

(您可以将此代码粘贴到控制台中,它将起作用)

本质上,该函数的作用是获取一个数字,创建一个数组并调用自身,而 array.length < number将到目前为止的计数插入数组。它将第一次调用中创建的数组传递给所有后续调用。

我对其进行了测试,它似乎完全按照预期工作。只是它的性能相当差。我测试了一下..

(这不是性感的代码)

test(5000, undefined, function(ar, start ){ 
    var finish = new Date; 
    console.log(
        ar.length,
        'timeTaken: ', finish - start 
    ); 
});

现在我显然想知道需要多长时间才能完成,上面的代码大约花了20秒。现在在我看来,JS 计数到 5000 不应该需要 20 秒。再加上它正在做一些计算和处理,以将项目插入数组。但 20 多岁还是有点陡。

所以我决定同时生成几个,看看这对浏览器性能和计算速度有何影响。

(代码并没有变得更性感)

function foo(){ 
test(5000, undefined, function(ar, start ){ var finish = new Date; console.log(ar.length, 'timeTaken: ', finish - start, 'issue: 1'  ) });
test(5000, undefined, function(ar, start ){ var finish = new Date; console.log(ar.length, 'timeTaken: ', finish - start, 'issue: 2'  ) });
test(5000, undefined, function(ar, start ){ var finish = new Date; console.log(ar.length, 'timeTaken: ', finish - start, 'issue: 3'  ) });
test(5000, undefined, function(ar, start ){ var finish = new Date; console.log(ar.length, 'timeTaken: ', finish - start, 'issue: 4'  ) });
test(5000, undefined, function(ar, start ){ var finish = new Date; console.log(ar.length, 'timeTaken: ', finish - start, 'issue: 5'  ) });
};

总共有五个,同时运行并且不会导致浏览器挂起。

进程结束后,所有结果几乎同时返回。全部完成大约需要 21.5 秒。这仅比单独运行慢 1.5 秒。但我在窗口中将鼠标移动到具有 :hover 的元素上。效果只是为了确保浏览器仍然响应,因此这可能会导致 1.5 秒的开销。

因此,由于这些函数显然是并行运行的,因此浏览器中留下了更多的计算资源。

有人能够解释一下这里发生的性能问题,并详细说明如何改进这样的功能吗?

我这样做只是为了发疯..

function foo(){
    var count = 100000000000000000000000000000000000000;  
    test(count, undefined, function(ar, start ){ var finish = new Date; console.log(ar.length, 'timeTaken: ', finish - start, 'issue: 1'  ) });
    test(count, undefined, function(ar, start ){ var finish = new Date; console.log(ar.length, 'timeTaken: ', finish - start, 'issue: 2'  ) });
    test(count, undefined, function(ar, start ){ var finish = new Date; console.log(ar.length, 'timeTaken: ', finish - start, 'issue: 3'  ) });
    test(count, undefined, function(ar, start ){ var finish = new Date; console.log(ar.length, 'timeTaken: ', finish - start, 'issue: 4'  ) });
    test(count, undefined, function(ar, start ){ var finish = new Date; console.log(ar.length, 'timeTaken: ', finish - start, 'issue: 5'  ) });
};

在我写这篇文章的整个过程中它一直在运行,并且仍在继续。浏览器没有提示或挂起。结束后我会添加完成时间。

最佳答案

setTimeout 没有 0ms 的最小延迟。最小延迟在 5ms-20ms 范围内,具体取决于浏览器。

我自己的个人测试表明 setTimeout 不会立即将您放回事件堆栈

Live Example

在再次调用之前它有一个任意的最小时间延迟

var s = new Date(),
    count = 10000,
    cb = after(count, function() {
        console.log(new Date() - s);    
    });

doo(count, function() {
    test(10, undefined, cb);
});
  • 并行运行 10000 个这样的程序,计数到 10 需要 500 毫秒。
  • 运行 100 数到 10 需要 60 毫秒。
  • 运行 1 计数到 10 需要 40 毫秒。
  • 运行 1 计数到 100 需要 400 毫秒。

显然,每个单独的 setTimeout 都必须等待至少 4ms 才能再次调用。但这就是瓶颈。 setTimeout 上的单独延迟。

如果您并行安排其中 100 个或更多,那么它就会起作用。

我们如何优化它?

var s = new Date(),
    count = 100,
    cb = after(count, function() {
        console.log(new Date() - s);    
    }),
    array = [];

doo(count, function() {
    test(10, array, cb);
});

在同一阵列上设置 100 个并行运行。这将避免主要瓶颈,即 setTimeout 延迟。

以上过程在 2 毫秒内完成。

var s = new Date(),
    count = 1000,
    cb = after(count, function() {
        console.log(new Date() - s);    
    }),
    array = [];

doo(count, function() {
    test(1000, array, cb);
});

7 毫秒内完成

var s = new Date(),
    count = 1000,
    cb = after(1, function() {
        console.log(new Date() - s);    
    }),
    array = [];

doo(count, function() {
    test(1000000, array, cb);
});

并行运行 1000 个作业大致是最佳的。但你会开始遇到瓶颈。数到 100 万仍然需要 4500 毫秒。

关于JavaScript 性能长时间运行的任务,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6864397/

相关文章:

javascript - 异步执行递归数据树构建?

javascript - 如何基于 Hexcode Jquery 显示货币符号?

JavaScript 不透明度 Internet Explorer 中的抗锯齿错误

javascript - 将 margin-top 设置为与高度相反的动画的最佳方法是什么?

c++ - 与神秘指针相关的多线程速度减慢

performance - 启用 Keep-Alive(页面速度)

sql - 'exists' 相关子查询中递归 CTE 的替代方案?

javascript - 如何使用两个按钮导航菜单?

MySQL根据特定顺序分页大数据

python - 了解递归函数中总值的存储位置 - Python