为什么是tail文件| tr(管道)比多行的 sed 或 perl 更快?

为什么是tail文件| tr(管道)比多行的 sed 或 perl 更快?

我有一个大约有一百万行的文件,如下所示:

"ID" "1" "2"
"00000687" 0 1
"00000421" 1 0
"00000421" 1 0
"00000421" 1 0

最后一行重复了超过一百万次。从中汲取灵感这个问题,我尝试了一些建议的解决方案,看看哪一个更快。我预计只有一个进程的解决方案会比具有管道的解决方案更快,因为它们只使用一个进程。但这些是我的测试结果:

  • tail -n +2 file.txt | tr -d \"

    $ time tail -n +2 file.txt | tr -d \" 1> /dev/null
    
    real    0m0,032s
    user    0m0,020s
    sys     0m0,028s
    
  • sed '1d;s/"//g' file.txt

    $ time sed '1d;s/"//g' file.txt 1> /dev/null
    
    real    0m0,410s
    user    0m0,399s
    sys     0m0,011s
    
  • perl -ne ' { s/"//g; print if $. > 1 }' file.txt

    $ time perl -ne ' { s/"//g; print if $. > 1 }' file.txt 1> /dev/null
    
    real    0m0,379s
    user    0m0,367s
    sys     0m0,013s
    

我多次重复测试,总是得到相似的数字。正如你所看到的,tail -n +2 file.txt | tr -d \"快多了比其他人。为什么?

答案1

这归结为正在完成的工作量。

您的tail | tr命令最终会执行以下操作:

  • tail:
    • 阅读直到换行;
    • 输出剩余的所有内容,而不关心换行符;
  • in tr,读取,不关心换行符,并输出除 '"' (固定字符)之外的所有内容。

sed在解释给定的脚本后,您的命令最终会执行以下操作:

  • 读取直到换行,累积输入;
  • 如果这是第一行,则删除它;
  • 解释正则表达式后,将所有双引号替换为空;
  • 输出处理后的行;
  • 循环直到文件末尾。

在解释给定的脚本后,您的 Perl 命令最终会执行以下操作:

  • 读取直到换行,累积输入;
  • 解释正则表达式后,将所有双引号替换为空;
  • 如果这不是第一行,则输出处理后的行;
  • 循环直到文件末尾。

对于大量输入来说,寻找换行符的代价是昂贵的。

答案2

主要是因为perl和sed分别处理每一行。

如果你让 perl 通过更大的块来处理输入,并稍微简化一下(见注释),你可以让它更快——但没有 tr 快:

time perl -ne ' { s/"//g; print if $. > 1 }' file.txt 1> /dev/null

real    0m0.617s
user    0m0.612s
sys     0m0.005s

time perl -pe 'BEGIN{<>;$/=\40960} s/"//g' file.txt >/dev/null

real    0m0.186s
user    0m0.177s
sys     0m0.009s

time tail -n +2 file.txt | tr -d \" 1> /dev/null

real    0m0.033s
user    0m0.031s
sys     0m0.023s

注意:不要使用perl -ne '... if $. > 1'awk 'NR == 1 { ... } /foo/ { ... }'

使用BEGIN{<>}andBEGIN{getline}代替。

读完第一行后,您可以非常确定后续行将不再是第一行:无需一次又一次检查。

答案3

tail.c 中的 tail_lines() :

      /* Use file_lines only if FD refers to a regular file for
         which lseek (... SEEK_END) works.  */

      if ( ! presume_input_pipe
           && S_ISREG (stats.st_mode)
           && (start_pos = lseek (fd, 0, SEEK_CUR)) != -1
           && start_pos < (end_pos = lseek (fd, 0, SEEK_END)))

end_pos = lseek (fd, 0, SEEK_END)是文件内容被跳过的地方。在 file_lines() 中,有向后扫描来计算换行符。

lseek() 是一个非常简单的系统调用,用于重新定位文件偏移以进行读/写。


哦,看来我错过了这个问题的微妙之处;)这都是关于逐行阅读与逐块阅读的。通常,最好将多个通道组合成一个复杂的通道。但这里的算法只需要第一个换行符。

Ole 的两部分 Perl 脚本sysread()说明了他如何从搜索第一个换行符切换到读取最大块。

tail正常向后工作时,它读取最后一个块并计算换行符。它从那里打印或读取倒数第二个块。

答案4

我感觉你想用,perl但速度太慢了。

perl是一个通用工具,它不会像tr.不过,你可以接近一下:

$ tail -n +2 file.txt | tr -d \" >/dev/null;
real    0m0.040s
user    0m0.030s
sys     0m0.032s

$ perl -e 'while(sysread(STDIN,$b,1)) {$b eq "\n" and last}
           while(sysread(STDIN,$b,131072)) {
             $b=~tr/\"//d; print $b
           }' < file.txt > /dev/null;
real    0m0.049s
user    0m0.045s
sys     0m0.004s

您可以避免tail并走得更快:

$ time (read; tr -d \") < file.txt >/dev/null
real    0m0.033s
user    0m0.021s
sys     0m0.012s

相关内容