为什么要避免使用子壳呢?

时间:2014-02-24 00:15:23

标签: bash subshell

我在Stack Overflow上看到了很多答案和评论 提到做一些事情以避免子壳。在一些 案例,给出了一个功能性原因 (通常,可能需要读取变量 在它内部分配的子shell之外),但在 在其他情况下,避免似乎被视为目的 在自身。例如

这是为什么?是为了风格/优雅/美丽?对于 性能(避免分叉)?为了防止可能 错误?还有别的吗?

4 个答案:

答案 0 :(得分:6)

有一些事情正在发生。

首先,当它仅发生一次时,分支子shell可能是不明显的,但如果你在循环中执行它,则会增加可衡量的性能影响。在Windows等平台上,性能影响也更大,因为分叉并不像现代Unixlikes那样便宜。

其次,分支子shell意味着你有多个上下文,并且在它们之间切换时会丢失信息 - 如果你改变代码以在子shell中设置变量,那么当子shell退出时该变量就会丢失。因此,你的代码中包含的子代数越多,以后修改它时就越需要小心,以确保你所做的任何状态更改都会实际持续存在。

请参阅BashFAQ #24,了解由子壳引起的令人惊讶的行为的一些例子。

答案 1 :(得分:1)

有时候例子很有用。

f='fred';y=0;time for ((i=0;i<1000;i++));do if [[ -n "$( grep 're' <<< $f )" ]];then ((y++));fi;done;echo $y

real    0m3.878s
user    0m0.794s
sys 0m2.346s
1000

f='fred';y=0;time for ((i=0;i<1000;i++));do if [[ -z "${f/*re*/}" ]];then ((y++));fi;done;echo $y

real    0m0.041s
user    0m0.027s
sys 0m0.001s
1000

f='fred';y=0;time for ((i=0;i<1000;i++));do if grep -q 're' <<< $f ;then ((y++));fi;done >/dev/null;echo $y

real    0m2.709s
user    0m0.661s
sys 0m1.731s
1000

正如您所看到的,在这种情况下,在子shell中使用grep和执行相同基本测试的参数扩展之间的差异在总体时间内接近100倍。

进一步提出问题,并考虑下面的评论,这些评论显然无法表明他们想要表明的内容,我检查了以下代码: https://unix.stackexchange.com/questions/284268/what-is-the-overhead-of-using-subshells

time for((i=0;i<10000;i++)); do echo "$(echo hello)"; done >/dev/null 
real    0m12.375s
user    0m1.048s
sys 0m2.822s

time for((i=0;i<10000;i++)); do echo hello; done >/dev/null 
real    0m0.174s
user    0m0.165s
sys 0m0.004s

这实际上远比我预期的要糟糕得多。实际上总体时间慢了两个数量级,并且在系统调用时间内几乎减少了三个数量级,这绝对是不可思议的。 https://www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html

请注意,证明这一点的目的是为了表明,如果您使用的测试方法很容易陷入使用,子shell grep或sed,或gawk(或内置的bash)的习惯,喜欢echo),这对我来说是一个坏习惯,我喜欢在快速黑客攻击时陷入困境,值得认识到这会有很大的性能影响,并且它可能值得花时间避免那些如果bash builtins可以原生地处理这项工作。

通过仔细检查大型程序对子shell的使用,并用其他方法替换它们,如果可能的话,我能够在刚刚完成的一组优化中削减大约10%的总执行时间(不是第一次,而不是最后,我已经完成了这个,它已经被优化了几次,所以获得另外10%实际上非常重要)

所以值得注意。

因为我很好奇,所以我想确认一下&#39; time&#39;在这告诉我们: https://en.wikipedia.org/wiki/Time_(Unix)

  

总CPU时间是CPU或CPU时间的组合   CPU为程序执行某些操作以及花费的时间   他们花了很多时间在程序上对内核执行系统调用   代表。当程序循环遍历数组时,它正在累积用户   CPU时间。相反,当程序执行诸如的系统调用时   exec或fork,它正在累积系统CPU时间。

正如您在特别是echo循环测试中所看到的那样,在对内核的系统调用方面,分支的成本非常高,这些分支确实加起来(700x !!!花在sys调用上的时间更长)。 / p>

我正处于解决其中一些问题的持续过程中,因此这些问题实际上与我以及喜欢该程序的用户的全球社区非常相关,也就是说,这不是一个奥术对我而言,它的真实世界,具有真正的影响。

答案 2 :(得分:0)

嗯,这是我对这个重要原因的解释:答案是#2!

即使是关于避免一个子shell,也没有什么性能提升......请叫我Obvious先生,但这种想法背后的概念与避免无用地使用<insert tool here> cat|grep sort|uniq背后的概念相同,{ {1}}甚至cat|sort|uniq等等。

这个概念是Unix philosophyESR summed up well引用KISS保持简单,愚蠢!

我的意思是,如果你编写一个脚本,你永远不知道它最终会如何被使用,所以你可以节省的每个小字节或周期都很重要,所以如果你的脚本最终会吃掉数十亿行输入,那将是通过那么多的forks / bytes / ...更优化。

答案 3 :(得分:0)

我认为一般的想法是,除非另有要求,否则避免创建额外的shell进程是有意义的。

但是,有太多的情况可以使用其中一种,而另一种情况比另一种情况更有意义,说一种方式总体上比另一种更好。在我看来,这是纯粹的情境。