【问题标题】:Why an async function takes more time to execute than a sync one?为什么异步函数比同步函数需要更多时间来执行?
【发布时间】:2020-06-05 10:18:39
【问题描述】:

我编写了两个递归函数,它们对数组中的数字求和。他们做同样的事情,一个是异步的,另一个是同步的。异步函数花费的时间大约是同步函数的 9 倍

异步函数不应该利用同时运行更多任务这一事实吗?

函数

// Asynchronously sum the numbers in array
async function sumAsync(arr){

    if(arr.length == 1) return arr[0];

    const half = arr.length/2;

    // Numbers on the left half
    const left = arr.filter((e, i) => {
        return i < half;
    });

    // Numbers on the right half
    const right = arr.filter((e, i) => {
        return i >= half;
    });

    // Recursive call
    const leftR = sumAsync(left);
    const rightR = sumAsync(right);

    // Wait for resolves
    await Promise.all([leftR, rightR]);

    return await leftR + await rightR;
}


// Synchronously sum the numbers in array
function sumSync(arr){

    if(arr.length == 1) return arr[0];

    const half = arr.length/2;

    // Numbers on the left half
    const left = arr.filter((e, i) => {
        return i < half;
    });

    // Numbers on the right half
    const right = arr.filter((e, i) => {
        return i >= half;
    });

    // Recursive call
    const leftR = sumSync(left);
    const rightR = sumSync(right);

    return leftR + rightR;
}

测试它们

(async () => {

    const LENGTH = 1048576; // 1024^2
    const arr = Array.from(Array(LENGTH), num => Math.random()*10 | 0);
    //  arr[1048576] <- random (0 ~ 9)

    // Async sum
    console.log('ASYNC');
    before = Date.now();
    console.log(`SUM: ${await sumAsync(arr)}`);
    after = Date.now();
    console.log(`TIME: ${after - before} ms`);

    // Sync sum
    console.log('SYNC');
    before = Date.now();
    console.log(`SUM: ${sumSync(arr)}`);
    after = Date.now();
    console.log(`TIME: ${after - before} ms`);

})();

结果

// ASYNC
// SUM: 4720832
// TIME: 5554 ms

// SYNC
// SUM: 4720832
// TIME: 613 ms

【问题讨论】:

    标签: javascript asynchronous recursion time async-await


    【解决方案1】:

    async 函数的返回值始终是 Promise,即使该函数只执行同步操作,并且 Promise 的 await(或 .then)只会在微任务期间运行以下内容(当前同步代码完成运行后)。对于一个大数组,这将导致很多不必要的微任务包装同步代码。

    当没有实际异步发生时,这只是额外的包袱,并导致需要额外的处理时间和功率。

    异步函数不应该利用同时运行更多任务的事实吗?

    Javascript 是单线程的,即使使用 async 函数也是如此。如果一次调用多个异步函数,则在任何时候只有 一个 代码路径可能是“活动的”。如果所有任务所需的总处理时间是 1000 毫秒,那么在标准 Javascript 中,至少要花费 1000 毫秒。

    您实际上并没有同时运行更多任务 - 您只是将任务包装在 Promise 中(不必要地),同时执行相同的工作。

    对于真正的并行操作,您必须使用当前环境提供的内容,例如 Node 中的 child_processweb worker

    【讨论】:

    • 这里的“tick”是什么?
    • 我认为它是一个比“微任务”更直观的词,它不会立即运行,而是在当前运行的代码完成之后。这也是 Node 将 nearly identical 称为微任务的东西。
    • 不幸的是,这在我看来有点误导,因为“tick”通常用于描述事件循环的完整迭代,而它根本不是。微任务将在解决它的任务结束后立即运行。所以如果你的任务以这个 Promise 解析结束,它实际上是同步运行的。
    • 是的,我明白你的意思了。想法来自process.nextTick(callback),但对于在callback 附近运行的东西来说,这是一个非常奇怪的名字,下一个滴答声之前
    【解决方案2】:

    简短版:异步一次只做一件事。它在队列中的任务之间切换(每个切换都有开销),当一个任务阻塞时,它将控制权交给另一个任务(切换开销,并在解除阻塞时重新排队阻塞的任务)。

    长版:异步并不意味着并行处理,它意味着交错(并发、协作)处理。即使使用异步,JavaScript 仍然是单线程的,并且您所做的所有实际工作都纯粹受 CPU 限制。事实上,你唯一真正的并发是异步代码将重复调度、暂停和恢复你的递归调用(但仍然一次只做一个工作),而同步代码将尽可能快地按顺序执行它们,不涉及事件循环。

    异步代码的好处是,当阻塞 I/O(包括等待用户输入之类的东西)正在执行时,该任务可以暂停,直到它被一些带外信号(I/O 完成,用户单击鼠标,无论如何),其他任务可以运行。好处是在大多数任务大部分时间都在等待某些东西的情况下获得并发(但不是并行)处理的好处,因此准备好运行的少数可以立即开始运行(因为它们通常是不运行时,任务切换的开销无关紧要;大多数时候,没有什么可切换的,当你没有更好的事情可做时,就会支付这么多的开销)。但这肯定比不停地进行数字运算要高。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-05-15
      • 2018-05-06
      • 1970-01-01
      • 2019-02-15
      • 1970-01-01
      • 2020-04-11
      • 2015-03-12
      • 1970-01-01
      相关资源
      最近更新 更多