我有一个大约有一百万行的文件,如下所示:
"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