关于 'while + read' .vs. 的性能问题AWK

标签 performance bash awk while-loop

我遇到了一个奇怪的问题。我有一个大文件(可能超过 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/

相关文章:

sql - 使用交叉应用子查询优化sql查询时出现问题

bash - 如何将 glob 表达式分配给 Bash 脚本中的变量?

linux - 从文件动态更新主机文件

linux - 将表转换为 2 列

linux - 用于在文件中编辑和添加条目的 Shell 脚本

bash - awk;通过 file1 搜索 file2

performance - 距离近似?

performance - 仅使用位操作的高效方法

windows - 无需任何第三方即可自行扩展的 Azure 应用程序的模式是什么?

bash - 终端程序如何向屏幕添加滚动?