我遇到了一个奇怪的问题。我有一个大文件(可能超过 1,000,000,000 行),其中仅包含一个表示文件大小的列。看起来像
55568
9700
7243
9692
63
5508
1679
14072
.....
我想计算每个值的出现次数。我使用两个不同的脚本
注意::下面使用的文件被剪切,只包含 10,000 行 !!!
bob@bob-ruby:~$ cat 1.sh
#!/bin/bash
while read size ; do
set -- $size
((count[$1]++))
done < file-size.txt
bob@bob-ruby:~$
bob@bob-ruby:~$ cat 2.sh
#!/bin/bash
awk '{count[$1]++}' file-size.txt
bob@bob-ruby:~$
而且我发现 1.sh(纯 shell 脚本)比 2.sh(awk 脚本)慢得多
bob@bob-ruby:~$ time bash 2.sh
real 0m0.045s
user 0m0.012s
sys 0m0.032s
bob@bob-ruby:~$ time bash 1.sh
real 0m0.618s
user 0m0.508s
sys 0m0.112s
bob@bob-ruby:~$
通过 'strace' 命令,我发现 1.sh 生成了很多系统调用,而 '2.sh' 却少得多,这是为什么?
“awk”是否在内部做了一些“魔术”工作?
bob@bob-ruby:~$ strace -c bash 1.sh
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
38.62 0.042011 1 30320 rt_sigprocmask
29.97 0.032597 2 20212 _llseek
15.33 0.016674 2 10115 read
12.57 0.013675 1 10106 10106 ioctl
(cut)
bob@bob-ruby:~$ strace -c bash 2.sh
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
95.52 0.008000 4000 2 1 waitpid
3.20 0.000268 21 13 5 access
1.28 0.000107 5 21 fstat64
0.00 0.000000 0 9 read
最佳答案
Chet Ramey(chet.ramey@case.edu)的回答
在 2012 年 12 月 21 日晚上 9:59,boblin 写道:
hi, chet :
I had meet a strange problem . I have a large file (maybe more than
10,000 lines) which contains only a single column which represent the size of file . It looks like
55568 9700 7243 9692 63 5508 1679 14072 .....
And I wants to count the occurences of each value. I use two different method
bob@bob-ruby:~$ cat 1.sh #!/bin/bash while read size ; do set -- $size ((count[$1]++)) done < file-size.txt bob@bob-ruby:~$
这确实是一种低效的方法,但还没有到它应该的程度 产生巨大的影响。没有必要仅仅为了装饰而使用 `set' 原因。你可以做
同时读取大小;做 ((计数[$大小]++)) 完成<文件大小.txt
bob@bob-ruby:~$ cat 2.sh #!/bin/bash awk '{count[$1]++}' file-size.txt bob@bob-ruby:~$
and I found that 1.sh (pure shell script) is much slower than 2.sh (awk-script)
bob@bob-ruby:~$ time bash 2.sh real 0m0.045s user 0m0.012s sys 0m0.032s bob@bob-ruby:~$ time bash 1.sh real 0m0.618s user 0m0.508s sys 0m0.112s bob@bob-ruby:~$
Through strace command , I found 1.sh generated lots of syscall , while the '2.sh' is much less , why is that ?
因为你没有跟踪 awk。您跟踪了 bash 调用和等待 哇哦。这就是 `waitpid' 支配执行时间的原因。
Is that the awk do any 'magic' work inside ?
awk 对其操作的限制要少得多,如下所述。
bob@bob-ruby:~$ strace -c bash 1.sh % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 38.62 0.042011 1 30320 rt_sigprocmask 29.97 0.032597 2 20212 _llseek 15.33 0.016674 2 10115 read 12.57 0.013675 1 10106 10106 ioctl
bash 经常调用 sigprocmask 有一个问题,因为它调用 setjmp 以 setjmp 保存和恢复信号掩码的方式。我 对信号和陷阱做了一些工作,这将允许下一个版本 避免恢复信号掩码。
lseeks 和 reads 必须保留。我想 awk 可以读取尽可能多的数据 它想要进入一个内部缓冲区并从内存中处理它。外壳是 需要将文件偏移量重置回它在每次之后消耗的内容 读取,因此它调用的程序可以获得预期的标准输入——它是 不允许在读取内置调用之间提前读取。这意味着 shell 必须测试它正在读取的文件描述符 每次运行 read 内置函数时都能够查找——终端和管道 无法在数据流中向后查找,因此 shell 必须读取一个 一次从那些字符。 shell 的 read 内置函数做了一些最小的 缓冲,所以即使对于 shell 可以向后查找的常规文件, 它必须调用 lseek 来调整文件指针,然后才能读取内置函数 返回一行。这也增加了所需的 read(2) 调用次数: 在某些情况下,shell 会多次从文件中读取相同的数据, 并且每次调用 read 至少需要一个 read(2) 调用 内置。
ioctl是判断输入的fd是否附加到一个 终端;除了无缓冲读取,几个选项是唯一的 使用终端时可用。每次调用至少有一个 lseek 到 read 内置函数,判断输入 fd 是否为管道。
这说明了您在 strace 输出中列出的系统调用。
切特
生命如此短暂,工艺如此漫长。'' - 乔叟
Ars longa, vita brevis'' - 希波克拉底
Chet Ramey,ITS,CWRU chet@case.edu http://cnswww.cns.cwru.edu/~chet/
关于关于 'while + read' .vs. 的性能问题AWK,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14048990/