【问题标题】:Why would promisify cause a loop to take massively longer?为什么 promisify 会导致循环花费更长的时间?
【发布时间】:2020-04-04 01:24:41
【问题描述】:

我有一个应用程序,它必须从视频中提取颜色信息,它通过分析每一帧来做到这一点。首先,我提取帧,然后将它们的位置数组加载到内存中。正如您可能想象的那样,即使是一个小视频也可能有数千个。

我用来提取每个帧颜色信息的函数是一个承诺,所以我选择使用 Promise.all 批量处理一组承诺

对于每个文件的绝对路径,我使用fs 读取文件,然后将其传递以进行处理。我已经使用许多独立图像完成了此操作,并且知道该过程只需要大约一秒钟,但突然间处理一张图像需要将近 20 分钟。我终于发现在fs.readFile 上使用promisify 是导致瓶颈的原因。我不明白为什么?

在第一个中,fs.readFile 在返回的 promise 内部进行了转换,而在第二个中,fs.readFile 只是正常使用,我等待调用 resolve。我不介意使用 non-promise 的,我只是好奇为什么这会导致这么慢?

在我停止使用promisify 的那一刻,应用程序速度恢复到 1 帧/秒

慢代码:

   async analyzeVideo(){
    await this._saveVideo();
    await this._extractFrames();
    await this._removeVideo();

    const colorPromises = this.frameExtractor.frames.map(file => {
      return new Promise(resolve => {
        //transform image into data
        const readFile = promisify(fs.readFile);
        readFile(file)
            .then(data => {
              const analyzer = new ColorAnalyzer(data);
              analyzer.init()
                  .then(colors => {
                    resolve(colors)
                  })
            })
            .catch((e)=> console.log(e));
      })
    });
    const colors = await runAllQueries(colorPromises);

    await this._removeFrames();

    this.colors = colors;

    async function runAllQueries(promises) {
      const batches = _.chunk(promises, 50);
      const results = [];
      while (batches.length) {
        const batch = batches.shift();
        const result = await Promise.all(batch)
            .catch(e=>console.log(e));
        results.push(result)
      }
      return _.flatten(results);
    }
  }

快速代码:

async analyzeVideo(){
    await this._saveVideo();
    await this._extractFrames();
    await this._removeVideo();
    const colorPromises = this.frameExtractor.frames.map(file => {
      return new Promise(resolve => {
        //transform image into data
        fs.readFile(file, (err, data) => {
          const analyzer = new ColorAnalyzer(data);
          analyzer.init()
              .then(colors => {
                resolve(colors)
              })
        });
      })
    });
    const colors = await runAllQueries(colorPromises);

    await this._removeFrames();

    this.colors = colors;

    async function runAllQueries(promises) {
      const batches = _.chunk(promises, 50);
      const results = [];
      while (batches.length) {
        const batch = batches.shift();
        const result = await Promise.all(batch)
            .catch(e=>console.log(e));
        results.push(result)
      }
      return _.flatten(results);
    }
  }

【问题讨论】:

  • 我怀疑是不是这个问题,但你不需要promisifyfs.readFile N 次,在模块开头做一次。
  • @MarcosCasagrande 这实际上是我发现 promisify 是问题所在。当我将它包含在new Promise() 之外时,它在实际调用承诺时是未定义的
  • 您的runAllQueries 没有意义。当您调用该函数时,所有 readFile 调用已经在运行,您已经创建了 Promise。
  • @Bergi colorPromises 持有承诺,但不会执行。
  • @KAT 承诺不是可以“执行”的东西。它代表已经开始计算的结果,可以等待,仅此而已。 Promise.all 不会“执行”任何东西,它只是创建一个新的承诺,等待所有个人承诺。立即执行执行器回调的是new Promise 调用。

标签: node.js asynchronous promise node-promisify


【解决方案1】:

您不需要在每次循环迭代中promisify,只需在模块顶部执行一次即可。

问题很可能是由从未解决的 Promise 引起的。您没有正确处理错误,因此如果抛出错误,Promise.all 可能永远无法完成。

除了在.catch 中记录错误之外,您还必须在reject 中记录错误,或者如果您不关心错误,至少要在resolve 中记录。 analyzer.init() 错误也没有被捕获(如果该函数可以拒绝)

const readFile = promisify(fs.readFile);
// ...

const colorPromises = this.frameExtractor.frames.map(file => {
   return new Promise((resolve, reject) => {
        //transform image into data
        // const readFile = promisify(fs.readFile);
        readFile(file)
            .then(data => {
              const analyzer = new ColorAnalyzer(data);
              return analyzer.init()   
            })
            .then(resolve) // colors
            .catch((e)=> { 
              reject(e);
              console.log(e)
            });
      })
})

除了runAllQueries 没有做你认为它正在做的事情。你已经执行了所有的承诺。

我建议你改用p-limit

const pLimit = require('p-limit');

const limit = pLimit(50);

/* ... */

const colorPromises = this.frameExtractor.frames.map(file => {
   return limit(() => {
        return readFile(file)
            .then(data => {
              const analyzer = new ColorAnalyzer(data);
              return analyzer.init()   
            })
            .then(resolve) // colors
    })
})

const colors = await Promise.all(colorPromises);

此外,如果您一次执行 50 次读取,则应增加 UV_THREADPOOL_SIZE 的值,默认为 4。

在您的入口点,在任何要求之前:

process.env.UV_THREADPOOL_SIZE = 64 // up to 128

或者调用脚本为:UV_THREADPOOL_SIZE=64 node index.js

【讨论】:

  • 我怀疑这是因为当逐步尝试调试函数被正确调用时,而不是抛出错误。循环运行速度非常慢。
  • 您是否有一个 sn-p,如果您可以重现该问题,我们可以运行吗?是否修复了我在顶部移动的promisify
  • 将 promisify 移到顶部并且只调用一次导致 readFile 在调用时不在范围内。每个承诺批次以 50 个文件迭代 appx 3000 个文件。
  • 您的代码没有这样做。有时间我会更新我的答案。但是您正在同时运行所有内容,即 3k 个文件。
  • 我很好奇runAllQueries 怎么不正确?声明承诺与执行承诺一样吗?
猜你喜欢
  • 2019-07-06
  • 2012-12-13
  • 1970-01-01
  • 2023-01-15
  • 1970-01-01
  • 2014-01-31
  • 1970-01-01
  • 2018-11-05
  • 1970-01-01
相关资源
最近更新 更多