为什么 wc 实用程序这么慢?
当我在大文件上运行它时,它花费的时间比 md5sum 长大约 20 倍:
MyDesktop:/tmp$ dd if=/dev/zero bs=1024k count=1024 of=/tmp/bigfile
1024+0 records in
1024+0 records out
1073741824 bytes (1.1 GB) copied, 0.687094 s, 1.6 GB/s
MyDesktop:/tmp$ time wc /tmp/bigfile
0 0 1073741824 /tmp/bigfile
real 0m45.969s
user 0m45.424s
sys 0m0.424s
MyDesktop:/tmp$ time md5sum /tmp/bigfile
cd573cfaace07e7949bc0c46028904ff /tmp/bigfile
real 0m2.520s
user 0m2.196s
sys 0m0.316s
这不仅仅是由于文件充满空值而导致的奇怪边缘条件,即使文件充满随机数据或者是文本文件,我也看到了相同的性能差异。
(这是在 Ubuntu 13.04,64 位上)
答案1
所以我去了源代码,看起来缓慢是在处理双字节字符时。本质上,对于读入的每个字符,都需要调用mbrtowc()
尝试将其转换为宽字符,然后测试该宽字符以查看它是否是单词分隔符、行分隔符等。
事实上,如果我更改LANG
默认的语言环境变量en_US.UTF-8
(UTF-8 是多字节字符集)并将其设置为“ C
”(简单的单字节字符集),wc
则能够使用单字节优化,从而大大加快速度,只需要以前的四分之一左右的时间。
此外,它只需检查每个字符是否进行单词 ( -w
)、行长度 ( -L
) 或字符 ( -m
) 计数。如果它只进行字节和/或行计数,它可以跳过宽字符处理,然后运行得非常快——比md5sum
.
我运行了它gprof
,用于处理多字节字符(mymbsinit()
、mymbrtowc()
、myiswprint()
等)的函数仅占用了大约 30% 的执行时间,并且单步执行缓冲区的代码要复杂得多,因为它必须处理可变大小字符在缓冲区中的可变大小步骤,以及将跨越缓冲区的任何部分完成的字符填充回缓冲区的开头,以便下次可以处理它。
现在我知道要寻找什么了,我发现了一些帖子提到一些实用程序的 utf-8 速度慢:
https://stackoverflow.com/questions/13913014/grepping-a-huge-file-80gb-any-way-to-speed-it-up http://dtrace.org/blogs/brendan/2011/12/08/2000x-performance-win/
答案2
只是一个猜测,但你有点将苹果与橙子进行比较,比较wc
正在做什么和md5sum
正在做什么。
md5sum的任务
当md5sum
处理文件时,它只是将文件作为流打开,然后开始通过MD5校验功能需要很少的内存。它本质上是 CPU 和磁盘 I/O 限制。
厕所的任务
当wc
运行时,它会做更多的事情,而不仅仅是一次解析文件一个字符。它必须实际分析文件的结构,一次一行地确定字符之间的边界在哪里以及是否是单词边界。
例子
考虑以下字符串以及每种算法在解析它们时必须如何遍历它们:
“Hello! Greg”
“Hello!Greg”
“Hello\nGreg”
“A.D.D.”
“Wow, how great!”
“wow \n\n\n great”
“it was a man-eating shark.”
对于 MD5,它每次在这些字符串中移动一个字符。因为wc
它必须决定什么是单词和行边界并跟踪它看到的出现次数。
其他厕所讨论
我找到了这个2006 年的编码挑战讨论wc
在 .NET 中的实现。当您查看一些伪代码时,困难是非常明显的,因此这可能有助于开始阐明为什么wc
看起来比其他操作慢得多。