我有一个必须从视频中提取颜色信息的应用程序,它通过分析每个帧来做到这一点。首先,我提取帧,然后将其位置的数组加载到内存中。就像您想象的那样,即使是一小段视频,它也可以成千上万。
我用来提取每个帧颜色信息的函数是一个Promise,因此我选择使用Promise.all
对于每个文件的绝对路径,我使用fs
读取文件,然后将其传递以进行处理。我已经使用许多独立的图像完成了此操作,并且知道该过程仅花费大约一秒钟的时间,但是突然间,它花费了将近20分钟的时间来处理一张图像。我终于发现,在promisify
上使用fs.readFile
是造成瓶颈的原因。我不明白为什么?
在第一个fs.readFile
中,它会在返回的承诺中进行转换,而在第二个fs.readFile
中,它通常会像往常一样使用,我等待被调用的决心。我不介意使用不承诺的服务,只是很好奇为什么这会导致速度变慢?
我停止使用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);
}
}
答案 0 :(得分:1)
您不需要在每次循环迭代中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