我已经完成了以下测试,并且在我的系统上,第二个脚本的结果差异大约长100倍。
我的文件是一个名为strace的输出 bigfile
$ wc -l bigfile.log
1617000 bigfile.log
剧本
xtian@clafujiu:~/tmp$ cat p1.sh
tail -n 1000000 bigfile.log | grep '"success": true' | wc -l
tail -n 1000000 bigfile.log | grep '"success": false' | wc -l
xtian@clafujiu:~/tmp$ cat p2.sh
log=$(tail -n 1000000 bigfile.log)
echo "$log" | grep '"success": true' | wc -l
echo "$log" | grep '"success": true' | wc -l
我实际上没有与grep匹配的任何东西,因此没有任何内容写入到的最后一个管道中 wc -l
以下是时间安排:
xtian@clafujiu:~/tmp$ time bash p1.sh
0
0
real 0m0.381s
user 0m0.248s
sys 0m0.280s
xtian@clafujiu:~/tmp$ time bash p2.sh
0
0
real 0m46.060s
user 0m43.903s
sys 0m2.176s
所以我通过strace命令再次运行了两个脚本
strace -cfo p1.strace bash p1.sh
strace -cfo p2.strace bash p2.sh
这是跟踪的结果:
$ cat p1.strace
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
97.24 0.508109 63514 8 2 waitpid
1.61 0.008388 0 84569 read
1.08 0.005659 0 42448 write
0.06 0.000328 0 21233 _llseek
0.00 0.000024 0 204 146 stat64
0.00 0.000017 0 137 fstat64
0.00 0.000000 0 283 149 open
0.00 0.000000 0 180 8 close
...
0.00 0.000000 0 162 mmap2
0.00 0.000000 0 29 getuid32
0.00 0.000000 0 29 getgid32
0.00 0.000000 0 29 geteuid32
0.00 0.000000 0 29 getegid32
0.00 0.000000 0 3 1 fcntl64
0.00 0.000000 0 7 set_thread_area
------ ----------- ----------- --------- --------- ----------------
100.00 0.522525 149618 332 total
和p2.strace
$ cat p2.strace
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
75.27 1.336886 133689 10 3 waitpid
13.36 0.237266 11 21231 write
4.65 0.082527 1115 74 brk
2.48 0.044000 7333 6 execve
2.31 0.040998 5857 7 clone
1.91 0.033965 0 705681 read
0.02 0.000376 0 10619 _llseek
0.00 0.000000 0 248 132 open
...
0.00 0.000000 0 141 mmap2
0.00 0.000000 0 176 126 stat64
0.00 0.000000 0 118 fstat64
0.00 0.000000 0 25 getuid32
0.00 0.000000 0 25 getgid32
0.00 0.000000 0 25 geteuid32
0.00 0.000000 0 25 getegid32
0.00 0.000000 0 3 1 fcntl64
0.00 0.000000 0 6 set_thread_area
------ ----------- ----------- --------- --------- ----------------
100.00 1.776018 738827 293 total
分析
毫不奇怪,在这两种情况下,大多数时间都花在等待过程完成上,但是p2等待的时间比p1长2.63倍,并且正如其他人所提到的,您在p2.sh中起步较晚。
因此,现在忘记了waitpid
,忽略该%
列,而查看两条迹线的秒数列。
可以理解,最长的时间 p1花费了大部分时间,这是因为可以读取的文件很大,但是p2的读取时间是p1的28.82倍。- bash
不会期望将如此大的文件读入一个变量,并且可能一次读取缓冲区,分成几行然后再获取另一个。
读取计数 p2为705k,而p1为84k,每次读取都需要将上下文切换到内核空间然后再移出。读取和上下文切换次数接近10倍。
在写入时间 P2在写比P1花费41.93倍以上
写计数 p1比p2进行的写操作更多,分别为42k和21k,但是它们要快得多。
可能是因为与尾写缓冲区相对应echo
的行数grep
。
而且,p2在写上花费的时间比在读上花费的时间更多,p1相反!
其他因素查看brk
系统调用的数量:p2的中断时间比读取时间长2.42倍!在p1中(甚至不注册)。brk
是因为程序最初没有分配足够的空间而需要扩展其地址空间时,这可能是由于bash必须将该文件读取到变量中,并且不希望它那么大,并且如@scai所述,如果文件变得太大,即使那样也无法正常工作。
tail
可能是一种非常有效的文件读取器,因为这是它设计的目的,它可能会对文件进行内存映射并扫描换行符,从而使内核可以优化I / O。bash的阅读和写作时间都不够好。
p2花费了44ms和41ms的时间clone
,execv
对于p1来说这不是可测量的量。大概bash读取并从尾部创建变量。
最终,总计 p1执行〜150k系统调用,而p2则执行740k(4.93倍)。
消除了waitpid,p1花费了0.014416秒执行系统调用,p2花费了0.439132秒(长30倍)。
因此,p2似乎在用户空间中大部分时间都花在了其他事情上,除了等待系统调用完成和内核重新组织内存之外,p1执行更多的写入操作,但是效率更高,并且显着减少了系统负载,因此速度更快。
结论
编写bash脚本时,我永远不会担心通过内存进行编码,这并不意味着您没有在尝试提高效率。
tail
设计用于执行其操作,可能memory maps
是处理文件,以便高效读取并允许内核优化I / O。
优化问题的一种更好的方法可能是先grep
处理'“ success”:'行,然后计算是非值,grep
并具有一个count选项,该选项再次避免了wc -l
,甚至更好的是,将尾巴通过awk
并计算出true和并发错误。p2不仅要花费很长时间,而且还会在内存被brks搅乱时增加系统的负载。