从node.js,shell grep或fs.readFile哪个更快?

从node.js,shell grep或fs.readFile哪个更快?,node.js,shell,grep,Node.js,Shell,Grep,我有一个长时间运行的node.js进程,需要扫描日志文件以查找模式。我至少有两个明显的选择:或者使用node.js读取文件并解析缓冲区/流。我还没有在intarwebs上找到这两种方法的比较。我的问题有两个: 哪个更快 为什么我更喜欢一种技术而不是另一种 分叉grep更简单、更快,而且grep很可能运行更快,使用更少的cpu。尽管fork的开销相当高(远大于打开文件),但您只需要fork一次并流式处理结果。另外,要从节点的文件i/o中获得良好的性能可能很棘手。为了回答这个问题,我编写了这个小程序

我有一个长时间运行的node.js进程,需要扫描日志文件以查找模式。我至少有两个明显的选择:或者使用node.js读取文件并解析缓冲区/流。我还没有在intarwebs上找到这两种方法的比较。我的问题有两个:

  • 哪个更快
  • 为什么我更喜欢一种技术而不是另一种

  • 分叉grep更简单、更快,而且grep很可能运行更快,使用更少的cpu。尽管fork的开销相当高(远大于打开文件),但您只需要fork一次并流式处理结果。另外,要从节点的文件i/o中获得良好的性能可能很棘手。

    为了回答这个问题,我编写了这个小程序

    #!/usr/local/bin/node
    'use strict';
    
    const fs = require('fs');
    const log = '/var/log/maillog';
    const fsOpts = { flag: 'r', encoding: 'utf8' };
    const wantsRe = new RegExp(process.argv[2]);
    
    function handleResults (err, data) {
        console.log(data);
    }
    
    function grepWithFs (file, done) {
        fs.readFile(log, fsOpts, function (err, data) {
            if (err) throw (err);
            let res = '';
            data.toString().split(/\n/).forEach(function (line) {
                if (wantsRe && !wantsRe.test(line)) return;
                res += line + '\n';
            });
            done(null, res);
        });
    };
    
    function grepWithShell (file, done) {
        const spawn = require('child_process').spawn;
        let res = '';
    
        const child = spawn('grep', [ '-e', process.argv[2], file ]);
        child.stdout.on('data', function (buffer) { res += buffer.toString(); });
        child.stdout.on('end', function() { done(null, res); });
    };
    
    for (let i=0; i < 10; i++) {
        // grepWithFs(log, handleResults);
        grepWithShell(log, handleResults);
    }
    
    文件系统是一对镜像SSD,它们通常足够快,不会成为瓶颈。结果如下:

    灰壳 格雷普威茨
    差别是巨大的。使用shell grep进程的速度要快得多。正如Andras指出的,节点的I/O可能很棘手,我没有尝试任何其他fs.read*方法。如果有更好的方法,请指出(最好是使用类似的测试场景和结果)。

    这是我的nodejs实现,结果与预期基本一致: 小文件的运行速度比分叉的grep快(最多2-3k短线的文件), 大文件运行速度较慢。文件越大,差异越大。 (也许正则表达式越复杂,差异就越小——参见 下面。)

    我用我自己的包快速 一次一行文件i/o;也许还有更好的,我不知道

    我看到了一个我没有调查的意外异常:下面的计时 用于常量字符串regexp
    /foobar/
    。当我把它改成
    /[f][o][o][b][a][r]/
    为了实际使用正则表达式引擎,grep放慢了速度 下降3倍,节点加速!grep的3倍减速可在计算机上重现 命令行

    filename = "/var/log/apache2/access.log";     // 2,540,034 lines, 187MB
    //filename = "/var/log/messages";             // 25,703 lines, 2.5MB
    //filename = "out";                           // 2000 lines, 188K (head -2000 access.log)
    //filename = "/etc/motd";                     // 7 lines, 286B
    regexp = /foobar/;
    
    child_process = require('child_process');
    qfgets = require('qfgets');
    
    function grepWithFs( filename, regexp, done ) {
        fp = new qfgets(filename, "r");
        function loop() {
            for (i=0; i<40; i++) {
                line = fp.fgets();
                if (line && line.match(regexp)) process.stdout.write(line);
            }
            if (!fp.feof()) setImmediate(loop);
            else done();
        }
        loop();
    }
    
    function grepWithFork( filename, regexp, done ) {
        cmd = "egrep '" + regexp.toString().slice(1, -1) + "' " + filename;
        child_process.exec(cmd, {maxBuffer: 200000000}, function(err, stdout, stderr) {
            process.stdout.write(stdout);
            done(err);
        });
    }
    
    结果:

    /**
    results (all file contents memory resident, no disk i/o):
    times in seconds, best run out of 5
    
    /foobar/
                 fork   fs
    motd        .00876  .00358  0.41 x  7 lines
    out         .00922  .00772  0.84 x  2000 lines
    messages    .0101   .0335   3.32 x  25.7 k lines
    access.log  .1367   1.032   7.55 x  2.54 m lines
    
    /[f][o][o][b][a][r]/
    access.log  .4244   .8348   1.97 x  2.54 m lines
    
    **/
    
    (上面的代码都是一个文件,我将其拆分以避免滚动条)

    编辑:要突出显示关键结果,请执行以下操作:

    185MB,254万行,搜索RegExp/[f][o][o][b][a][r]/:

    格雷普威茨 已用时间:.83秒

    格雷普威霍克
    已用时间:.42秒

    为什么不尝试两者并进行比较?一般来说,答案是:您可以轻松使用并且速度足够快。如果您在大文件或大文件树上追求纯速度,请尝试执行
    ag
    。它比
    grep
    快得多,而且应该很难与手工编写的
    javascript
    相比
    i中的40是什么
    
    $ time node logreader.js 3E-4C03-86DD-FB6EF
    
    real    0m6.599s
    user    0m5.710s
    sys     0m1.751s
    
    filename = "/var/log/apache2/access.log";     // 2,540,034 lines, 187MB
    //filename = "/var/log/messages";             // 25,703 lines, 2.5MB
    //filename = "out";                           // 2000 lines, 188K (head -2000 access.log)
    //filename = "/etc/motd";                     // 7 lines, 286B
    regexp = /foobar/;
    
    child_process = require('child_process');
    qfgets = require('qfgets');
    
    function grepWithFs( filename, regexp, done ) {
        fp = new qfgets(filename, "r");
        function loop() {
            for (i=0; i<40; i++) {
                line = fp.fgets();
                if (line && line.match(regexp)) process.stdout.write(line);
            }
            if (!fp.feof()) setImmediate(loop);
            else done();
        }
        loop();
    }
    
    function grepWithFork( filename, regexp, done ) {
        cmd = "egrep '" + regexp.toString().slice(1, -1) + "' " + filename;
        child_process.exec(cmd, {maxBuffer: 200000000}, function(err, stdout, stderr) {
            process.stdout.write(stdout);
            done(err);
        });
    }
    
    function fptime() { t = process.hrtime(); return t[0] + t[1]*1e-9 }
    
    t1 = fptime();
    if (0) {
        grepWithFs(filename, regexp, function(){
            console.log("fs done", fptime() - t1);
        });
    }
    else {
        grepWithFork(filename, regexp, function(err){
            console.log("fork done", fptime() - t1);
        });
    }
    
    /**
    results (all file contents memory resident, no disk i/o):
    times in seconds, best run out of 5
    
    /foobar/
                 fork   fs
    motd        .00876  .00358  0.41 x  7 lines
    out         .00922  .00772  0.84 x  2000 lines
    messages    .0101   .0335   3.32 x  25.7 k lines
    access.log  .1367   1.032   7.55 x  2.54 m lines
    
    /[f][o][o][b][a][r]/
    access.log  .4244   .8348   1.97 x  2.54 m lines
    
    **/