为什么wc这么慢?


17

为什么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位上)


请注意那些只关心行数的人:wc -l <​​filename>在非常大的文件上要快得多。
EL

Answers:


27

因此,我去了源代码,看来处理双字节字符的速度很慢。本质上,对于每个读入的字符,它都需要调用mbrtowc()以尝试将其转换为宽字符,然后对该宽字符进行测试以查看其是否为单词分隔符,行分隔符等。

实际上,如果我更改LANG了默认的语言环境变量en_US.UTF-8(UTF-8是多字节字符集)并将其设置为“ C”(简单的单字节字符集),wc便可以使用单字节优化,从而大大加快了速度,只需大约四分之一的时间

此外,它只需要检查每个字符是否在计算单词(-w),行长(-L)或字符(-m)的数量。如果仅执行字节和/或行计数,则可以跳过宽字符处理,然后运行速度非常快-比md5sum

我跑它通过gprof,以及用于处理该多字节字符(的功能mymbsinit()mymbrtowc()myiswprint()等)占用了约的单独的执行时间的30%,以及将码通过缓冲步骤要复杂得多,因为它必须处理可变大小的字符的缓冲区中的可变大小的步骤,以及将跨越缓冲区的所有部分完成的字符塞回到缓冲区的开头,以便下次可以处理。

现在我知道要查找的内容了,我发现了一些帖子,其中提到了一些实用程序对utf-8的慢度:

/programming/13913014/grepping-a-huge-file-80gb-any-way-to-speed-it-up http://dtrace.org/blogs/brendan/2011/12/08 / 2000x-性能双赢/


2
哦,刚意识到您是OP。:p
Ivan Chau

2
尽管这是最受支持的答案,但这无关紧要。md5sum永远不会让您计算字数,wc也不会计算文件的md5哈希值!这就像问为什么在写文字时,我的车比打字机要慢吗?
user49468

5
@ user49468:可以合理地假设两者都是IO绑定的,因为两者都必须读取输入文件的每个字节。该答案证明,wc在处理多字节字符时,实际上是受CPU限制的。
MSalters

2
@ user49468:wc和md5sum可能做不同的事情,但是都读取文件并进行相对简单的计算,一个计算校验和,一个计算字节,分隔符和换行符。好吧,我认为这很简单,但是没有考虑到多字节字符集的额外复杂性。这更像是在问“为什么我的车去商店要比我的微型货车快20倍?” 您会期望两者之间有一些差异,但不会有20倍的差异。
约翰尼,

1
@Johnny,您的汽车/小型货车比较缺乏两者都旨在将您运送到商店的方面。因此可以进行速度比较。将您的汽车与条纹喷漆车进行比较更合适。仅仅因为两个人都在大街上,他们的速度并不重要,因为条纹画家不适合去购物,反之亦然。
user49468

1

只是一个猜测,但是您在比较wc做什么与做什么之间就比较了苹果和橙子md5sum

md5sum的任务

md5sum处理一个文件,它就会打开该文件作为一个流,然后开始运行通过流MD5校验功能,它需要很少的内存。它本质上与CPU和磁盘I / O绑定。

wc的任务

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它必须确定什么是单词和行的边界,并跟踪其出现的次数。

WC的其他讨论

我发现了2006年的编码挑战,其中讨论了wc在.NET中的实现。当您查看一些伪代码时,这些困难非常明显,因此这可能有助于开始弄清为什么它wc看起来比其他操作要慢得多。


1
您所描述的东西与标准Unix wc命令(至少不是Ubuntu随附的命令)不同。那个wc不算唯一的单词,只算单词,因此“ hello hello world”是3个单词,而不是2
Johnny

基于此理论,听起来像是简单的任务,例如计算行数,将会更快。更改“ wc”以指定行数是否会大大改变结果?'wc -l'–
约书亚·米勒

@Johnny-我从未说过,它不算您说过的独特单词。wc解析文件时会计算多个内容。它在分析文件时计算字,行和字节的数量。阅读手册页!
slm

@JoshuaMiller-不清楚是否wc仅计数行限制了它的内部解析,以便它仅计数这些内容,或者仅报告行结果,即使它仍然计数所有内容。
slm

@slm您确实说过它包含唯一的单词,您的示例说 “你好!Greg”会产生Hello 1,Greg 1,即每个单词的计数。与您链接的.Net项目说:“其主要任务之一是遍历一组数据并计算给定单词的重复次数。例如,给定句子“ Hello,yes hello”,它会告诉您“ Hello”一词被使用了两次,“ yes”一词被使用了一次。” 实际上,回声的结果是“你好,你好”。wc --words是“ 3”,而不是“ Hello:2,Yes:1”
Johnny
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.