为什么 promisify 会导致循环花费更长的时间?
Why would promisify cause a loop to take massively longer?
我有一个应用程序必须从视频中提取颜色信息,它通过分析每一帧来实现。首先,我提取帧,然后将它们的位置数组加载到内存中。正如您所想象的那样,即使是一个小视频,它也可能达到数千个。
我用来提取每个帧颜色信息的函数是一个 promise,所以我选择使用 Promise.all
对一组 promise 进行批处理
使用每个文件的绝对路径,我用 fs
读取文件,然后将其传递以进行处理。我已经用许多独立的图像完成了这个,并且知道这个过程只需要大约一秒钟,但突然间处理一张图像需要将近 20 分钟。我终于弄明白,在 fs.readFile
上使用 promisify
是导致瓶颈的原因。我不明白的是为什么?
在第一个中,fs.readFile
在返回的 promise 内部进行了转换,而在第二个中,fs.readFile
只是像往常一样使用,我等待 resolve 被调用。我不介意使用非承诺的,我只是好奇为什么这会导致这么慢?
我停止使用的那一秒 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);
}
}
您不需要在每个循环迭代中promisify
,只需在模块顶部执行一次即可。
问题很可能是由从未解决的承诺引起的。您没有正确处理错误,因此如果抛出错误,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
我有一个应用程序必须从视频中提取颜色信息,它通过分析每一帧来实现。首先,我提取帧,然后将它们的位置数组加载到内存中。正如您所想象的那样,即使是一个小视频,它也可能达到数千个。
我用来提取每个帧颜色信息的函数是一个 promise,所以我选择使用 Promise.all
使用每个文件的绝对路径,我用 fs
读取文件,然后将其传递以进行处理。我已经用许多独立的图像完成了这个,并且知道这个过程只需要大约一秒钟,但突然间处理一张图像需要将近 20 分钟。我终于弄明白,在 fs.readFile
上使用 promisify
是导致瓶颈的原因。我不明白的是为什么?
在第一个中,fs.readFile
在返回的 promise 内部进行了转换,而在第二个中,fs.readFile
只是像往常一样使用,我等待 resolve 被调用。我不介意使用非承诺的,我只是好奇为什么这会导致这么慢?
我停止使用的那一秒 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);
}
}
您不需要在每个循环迭代中promisify
,只需在模块顶部执行一次即可。
问题很可能是由从未解决的承诺引起的。您没有正确处理错误,因此如果抛出错误,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