15

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

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

4

4 回答 4

10

有几件事正在发生。

首先,当子shell 只发生一次时,它可能不会被注意到,但如果你在循环中执行它,它会增加可衡量的性能影响。在 Windows 等平台上,性能影响也更大,因为它的分叉不像现代 Unix 那样便宜。

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

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

于 2014-02-24T01:51:53.187 回答
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 -使用子shell的开销

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

请注意,演示这一点的目的是表明,如果您使用的测试方法很容易养成使用习惯,则可以使用 subshel​​l grep 或 sed 或 gawk(或内置 bash,如 echo),它适用于我是一个坏习惯,我在快速破解时往往会养成,值得意识到这将对性能产生重大影响,如果 bash 内置程序可以本地处理这项工作,那么可能值得花时间避免这些。

通过仔细检查使用子shell 的大型程序,并在可能的情况下用其他方法替换它们,我能够在刚刚完成的一组优化中减少大约 10% 的总执行时间(不是第一个,也不是最后一个,我做这个的时候,已经优化了好几次了,所以再增加10%其实很重要)

所以值得警惕。

因为我很好奇,所以我想在这里确认“时间”告诉我们什么: https://en.wikipedia.org/wiki/Time_(Unix)

总 CPU 时间是 CPU 或 CPU 为程序执行某些操作所花费的时间与代表程序为内核执行系统调用所花费的时间的组合。当程序循环遍历数组时,它正在累积用户 CPU 时间。相反,当程序执行诸如 exec 或 fork 之类的系统调用时,它正在累积系统 CPU 时间。

正如您在回声循环测试中所看到的,分叉的成本在对内核的系统调用方面非常高,这些分叉确实加起来(700 倍!!!更多的时间花在系统调用上)。

我正在解决其中一些问题,所以这些问题实际上与我以及喜欢该程序的全球用户社区非常相关,也就是说,这对我来说不是一个神秘的学术点,这是真实的世界,具有真实的影响。

于 2017-07-29T19:17:37.513 回答
0

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

然而,有太多的情况可以使用任何一种,一种比另一种更有意义,说一种方法总体上比另一种更好。在我看来,这纯粹是情境性的。

于 2014-02-24T04:56:56.847 回答
0

好吧,这是我对为什么这很重要的解释:这是答案#2!

即使是在避免一个子shell时,性能也有不少提升……叫我 Obvious 先生,但这种想法背后的概念与避免无用使用<insert tool here>likecat|grepsort|uniq什至cat|sort|uniq等背后的概念是相同的。

这个概念就是Unix 哲学, ESR 通过对KISS的引用很好地总结了它:保持简单,愚蠢!

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

于 2014-02-24T01:23:49.143 回答