Nodejs, 多 IO 操作, 如何提高执行效率?

Nodejs, 多 IO 操作, 如何提高执行效率?

主要操作:(文件去重)

function files_do(){
    let MD5 = 读(buffer 生成 MD5);
    if(从数据库找相同 MD5 的值.Count()==0){
       //没找到
       数据库.insert(文件信息);
    }else{
       //有相同值
       文件移动(原路径, 目标路径);
    }
    setTimeout(function(){
    	files_do()
    },0)
}

目前 5 万个文件(大小: 几 k 到十几 M 都有), 大概需要 6 个小时多..

需要处理几百万个文件.

有什么办法能进一步提高执行效率??


16 回复

先分析性能消耗在哪里。可能的优化方法有数据库所有的 MD5 放到 Set 里面,这样就直接干掉数据库了、多线程 /多进程、用 C 重写。


粗略的想了一下,主要的消耗有两个,一是读文件, IO 消耗比较大,二是数据库查询,消耗也不小。第一个没办法,如果你对内容要求不多,直接对整个文件做校验或许就可以了。数据库的话就像楼上说的,几万个也不多,直接存在内存里,然后统一写数据库。

不要直接比较 md5 ,先比较文件长度,再比较文件的部分内容(比如开头 1 KiB ),最后比较 md5 。

不要直接对比 md5 先对比文件大小 相同大小的文件再做 md5 对比

谢谢大家!!!
1, 我目前就用的内存数据库, 定期写回 db 文件里…所以目前数据库查询效率还可以.
2, “先比较文件大小” 不可行…因为 db.insert(文件信息) 必须有 MD5 值(因为后面的文件需要和它比对)…也还是得读一遍文件 buffer…
3, 现在考虑用 C++写 多线程读 buffer,并生成 MD5 的操作…先写入数据库…全部结束后, 数据库查重复, 再移动文件.

楼上
的建议不错。
读文件的时候不要读整个文件求 md5.

可以分三步:
1) 按照文件大小和文件前 256bytes 的 md5 来确定可能会重复的文件
2) 对 1 的结果求整个文件的 md5, 确定重复的文件
3) 去重

读 buffer 生成 MD5 可以换成调用 md5 终端指令。也许能改善,取决于 node 的 md5 实现如何。

md5 没啥性能瓶颈的,一般单核每秒都有几百兆。楼主需要先看一下 node CPU 是否跑满单核了,按照楼主 po 的代码来看是有异步性能浪费的( setTimeout ),当然实际代码也许不是长成这样。如果单核已经跑满了,可以考虑上多个进程并行运行。
优化的上线应当接近于这 5 万个文件全部读取一遍的性能。

另外楼主可以考虑直接将文件流 pipe 到 hash ,可以避免频繁的 buffer alloc ,进一步压榨一些性能。
用 C++ 生成 md5 就不用想了, nodejs 人家不是用 js 算的 md5 ,是 openssl 计算的,自己写的效率一般不会更高。

当然上面说的那些的前提是不频繁访问数据库。数据库一般是个瓶颈,一楼已经给出优化数据库的方法了。
不过不建议用 C 重写 :P 这个需求的终极瓶颈理论上在 IO ,如果测下来瓶颈不在 IO 上那么就是可以优化的。以及既然终极瓶颈是 IO ,那么我猜测不用异步写 C 的话会写得单核性能比 nodejs 版还要低。

谢谢给出的建议!!!
我的代码确实有用到 setTimeout ,因为希望页面上能反应出进度,给 dom 操作留点时间。(electron 小程序,数据库用的 LokiJS)
整体代码就是递归循环整个文件夹,找到所有的文件去重。重复文件放在固定文件夹,等待人工检查。
cpu 目前占用 20%不到(18%— 19%左右)。

你玩 telegram 吗?喵?

breezewish

这么来说目测你的 IO 用的也都是同步的 IO 了……?那么你首先要做的是全改成异步的 IO ,然后用上一些异步流程控制的库比如 caolan/async 来进行控制(比如可以用上它的 queue )。然后 setTimeout 可以去掉了

你把文件合并一下 大体看看读取 500m 是个啥速度 按道理 应该不慢 另外读取文件有异步和同步两种方式

在Node.js中处理多IO操作时,提高执行效率的关键在于利用Node.js的异步非阻塞特性,避免阻塞事件循环。以下是一些常见策略和示例代码:

  1. 使用异步函数: Node.js的内置模块(如fshttp等)提供了异步方法,可以在不阻塞事件循环的情况下执行IO操作。

    const fs = require('fs').promises;
    
    async function readFileAsync(filePath) {
        try {
            const data = await fs.readFile(filePath, 'utf8');
            console.log(data);
        } catch (err) {
            console.error(err);
        }
    }
    
    readFileAsync('example.txt');
    
  2. 并发处理: 使用Promise.allasync/await结合循环来并发处理多个IO操作。

    async function readMultipleFiles(filePaths) {
        try {
            const results = await Promise.all(filePaths.map(filePath => fs.readFile(filePath, 'utf8')));
            results.forEach(data => console.log(data));
        } catch (err) {
            console.error(err);
        }
    }
    
    readMultipleFiles(['file1.txt', 'file2.txt']);
    
  3. 流(Streams): 对于大文件或长时间运行的IO操作,使用流可以逐块处理数据,减少内存占用。

    const fs = require('fs');
    
    const readStream = fs.createReadStream('largeFile.txt', { encoding: 'utf8' });
    
    readStream.on('data', chunk => {
        console.log(chunk);
    });
    
    readStream.on('error', err => {
        console.error(err);
    });
    
    readStream.on('end', () => {
        console.log('File read complete');
    });
    

通过这些方法,你可以有效地提高Node.js在处理多IO操作时的执行效率。

回到顶部