【问题标题】:Make Javascript Threads Fast使 Javascript 线程快速
【发布时间】:2019-05-22 03:41:02
【问题描述】:

最近我一直在尝试使用 Web workers 接口来试验 JavaScript 中的线程。

尝试使用网络工作者制作包含,请执行以下步骤:

  • 将初始数组拆分为大小相等的部分
  • 为每个在该片段上运行 .contains 的片段创建一个网络工作者
  • 如果在任何片段中找到该值,则返回 true,而无需等待所有工作人员完成。

这是我尝试过的:

var MAX_VALUE = 100000000;
var integerArray = Array.from({length: 40000000}, () => Math.floor(Math.random() * MAX_VALUE));

var t0 = performance.now();
console.log(integerArray.includes(1));
var t1 = performance.now();
console.log("Call to doSomething took " + (t1 - t0) + " milliseconds.");

var promises = [];
var chunks = [];
while(integerArray.length) {
    chunks.push(integerArray.splice(0,10000000));
}

t0 = performance.now();
chunks.forEach(function(element) {
    promises.push(createWorker(element));
});

function createWorker(arrayChunk) {
    return new Promise(function(resolve) {
        var v = new Worker(getScriptPath(function(){
            self.addEventListener('message', function(e) {
                var value = e.data.includes(1);
                self.postMessage(value);
            }, false);
        }));
        v.postMessage(arrayChunk);
        v.onmessage = function(event){
            resolve(event.data);
        };
    });
}

firstTrue(promises).then(function(data) {
    // `data` has the results, compute the final solution
    var t1 = performance.now();
    console.log("Call to doSomething took " + (t1 - t0) + " milliseconds.");
});

function firstTrue(promises) {
    const newPromises = promises.map(p => new Promise(
        (resolve, reject) => p.then(v => v && resolve(true), reject)
));
   newPromises.push(Promise.all(promises).then(() => false));
    return Promise.race(newPromises);
}

//As a worker normally take another JavaScript file to execute we convert the function in an URL: http://stackoverflow.com/a/16799132/2576706
function getScriptPath(foo){ return window.URL.createObjectURL(new Blob([foo.toString().match(/^\s*function\s*\(\s*\)\s*\{(([\s\S](?!\}$))*[\s\S])/)[1]],{type:'text/javascript'})); }

任何浏览器和cpu都试过了,与只对初始数组做一个简单的包含相比,速度非常慢。

为什么这么? 上面的代码有什么问题?

参考文献

编辑:问题不在于具体的 .contains() ,而可能是其他数组函数,例如.indexOf()、.map()、forEach() 等。为什么在 web worker 之间拆分工作需要更长的时间...

【问题讨论】:

  • 我打算做更多的研究并发表更好的评论,但我想知道这是否可能是由于本机 includes 方法的性能缓慢造成的。

标签: javascript web-worker


【解决方案1】:

与简单的contains 相比,您在t0t1 之间做的工作要多得多。这些额外的步骤包括:

  1. 转换函数 -> 字符串 -> 正则表达式 -> blob -> 对象 URL
  2. 调用新的worker -> 解析对象URL -> JS引擎解释代码
  3. 发送网络工作数据 -> 在主线程上序列化 -> 在工作线程中反序列化(可能在实际复制的内存结构中,所以不会很慢)

你最好先创建线程,然后不断地向它传递数据。它可能不会更快,但不会锁定您的 UI。 另外,如果您反复搜索数组,我建议您将其转换为一个映射,其中键是数组值,值是索引。

例如 数组['apple', 'coconut', 'kiwi'] 将转换为{ apple: 1, coconut: 2, kiwi:3 } 搜索地图将在平摊的正常时间内进行(快速),而数组将是线性搜索(对于大型集合来说非常慢)。

【讨论】:

  • hm.. 也许你是对的......我尝试改变“promises.push(createWorker(element));”到“promises.push(createWorker());”和“var value = e.data.includes(1);”到“var 值 = 假;”而且速度更快...似​​乎将数据复制到线程是一个问题...您能否提供一些代码如何稍后传递数据或者我可以共享同一个数组而不复制它吗?
【解决方案2】:

这是一个人为的示例,因此很难帮助优化您正在尝试执行的具体操作,但一个容易被忽视且可修复的慢速路径是将数据复制到网络工作者。如果可能,您可以使用 ArrayBuffers 和 SharedArrayBuffers 快速将数据传入和传出 web worker。

您可以使用 postMessage 函数的第二个参数将 arrayBuffer 的所有权转移给 web worker。重要的是要注意,该缓冲区将不再可供主线程使用,直到它被 web worker 传回。 SharedArrayBuffers 没有这个限制,可以同时被许多工作人员读取,但出于安全考虑,不一定在所有浏览器中都支持(请参阅mdn for more details

例如

const arr = new Float64Array(new ArrayBuffer(40000000 * 8));

console.time('posting');
ww.postMessage(arr, [ arr.buffer ]);
console.timeEnd('posting');

运行大约需要 0.1 毫秒

const arr = new Array(40000000).fill(0);

console.time('posting');
ww.postMessage(arr, [ arr ]);
console.timeEnd('posting');

运行大约需要 10000 毫秒。这只是为了传输消息中的数据,而不是运行工作逻辑本身。

您可以阅读有关 postMessage transferList 参数 here 和可转移类型 here 的更多信息。重要的是要注意,您的示例进行时间比较的方式也包括 Web Worker 创建时间,但希望这可以更好地了解大部分时间的去向以及如何更好地解决它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-11-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-06
    • 2018-05-13
    • 1970-01-01
    相关资源
    最近更新 更多