如何计算分叉(子?)进程的数量

时间:2013-07-18 08:36:30

标签: bash process subprocess

其他人已经编写了(TM)一些bash脚本,它会分支很多子进程。它需要优化。但我正在寻找一种方法来衡量问题的“有多糟糕”。

我能/我怎样才能得到一个计数,说明这个脚本全部/递归地分叉了多少个子进程?

这是现有的分叉代码的简化版本 - 穷人的grep:

#!/bin/bash

file=/tmp/1000lines.txt
match=$1

let cnt=0
while read line
do
    cnt=`expr $cnt + 1`
    lineArray[$cnt]="${line}"
done < $file
totalLines=$cnt

cnt=0
while [ $cnt -lt $totalLines ]
do
    cnt=`expr $cnt + 1`
    matches=`echo ${lineArray[$cnt]}|grep $match`
    if [ "$matches" ] ; then
        echo ${lineArray[$cnt]}
    fi
done

脚本需要20秒才能在1000行输入中查找$1。这段代码分叉的过程太多了。在实际代码中,使用progA | progB | progCgrepcutawk等在每一行上运行更长的管道(例如sed)。< / p>

这是一个繁忙的系统,还有很多其他的东西在继续,所以在脚本运行期间对整个系统分叉了多少进程的数量对我来说有点用处,但我更喜欢此脚本和后代启动的进程计数。我想我可以分析脚本并自己计算,但脚本很长而且相当复杂,所以我只想用这个计数器进行调试,如果可能的话。

澄清:

  • 我不是在任何给定时间查找$$下的进程数(例如,通过ps),而是在脚本的整个生命周期中运行的进程数。
  • 我也不是在寻找这个特定示例脚本的更快版本(我可以这样做)。我正在寻找一种方法来确定首先使用bash内置函数优化30多个脚本中的哪一个。

1 个答案:

答案 0 :(得分:3)

您可以简单地捕获SIGCHLD信号来计算fork个进程。如果您可以编辑脚本文件,则可以执行以下操作:

set -o monitor # or set -m
trap "((++fork))" CHLD

所以fork变量将包含分叉数。最后您可以打印此值:

echo $fork FORKS

对于1000行输入文件,它将打印:

3000 FORKS

此代码分叉有两个原因。每个expr ...一个,`echo ...|grep...`一个。因此,在读取while循环时,每次读取一行时都会fork;在处理while循环时fork s 2次(一次是因为expr ...而一次是`echo ...|grep ...`)。因此,对于1000行文件,它会分叉3000次。

但这不确切!它只是调用shell完成的分叉。还有更多分叉,因为`echo ...|grep...`分叉启动来运行此代码。但在它也分叉两次之后:一个用于echo,一个用于grep。实际上它是3 fork s,而不是一个。所以它是5000 FORKS,而不是3000。

如果您需要计算叉子(叉子的叉子......)的叉子(或者您不能修改bash脚本或者您希望它从其他脚本执行),更精确的解决方案可以是使用

strace -fo s.log ./x.sh

它会打印这样的行:

30934 execve("./x.sh", ["./x.sh"], [/* 61 vars */]) = 0

然后你需要使用类似的东西计算唯一的PID(第一个数字是PID):

awk '{n[$1]}END{print length(n)}' s.log

如果是这个脚本,我得到5001(+1是原始脚本的PID)。

<强>评论

实际上,在这种情况下,可以避免所有fork

而不是

cnt=`expr $cnt + 1`

使用

((++cnt))

而不是

matches=`echo ${lineArray[$cnt]}|grep $match`
if [ "$matches" ] ; then
    echo ${lineArray[$cnt]}
fi

您可以使用的内部模式匹配:

[[ ${lineArray[cnt]} =~ $match ]] && echo ${lineArray[cnt]}

请注意 =~使用ERE而非RE(如grep)。因此它的行为类似于(或grep -E),而不是

我假设定义的lineArray不是没有意义的(否则在读取循环中可以测试匹配并且不需要lineArray)并且它也用于其他目的。在这种情况下,我可能会建议一个更短的版本:

readarray -t lineArray <infile 

for line in "${lineArray[@]}";{ [[ $line} =~ $match ]] && echo $line; }

第一行在没有任何循环的情况下读取完整的infilelineArray。第二行是逐个元素处理数组。

<强>措施

1000行的原始脚本(在上):

$ time ./test.sh
3000 FORKS

real    0m48.725s
user    0m14.107s
sys     0m30.659s

修改版

FORKS

real    0m0.075s
user    0m0.031s
sys     0m0.031s

上的相同内容:

3000 FORKS

real    0m4.745s
user    0m1.015s
sys     0m4.396s

FORKS

real    0m0.028s
user    0m0.022s
sys     0m0.005s

因此,此版本根本不使用fork(或clone)。我可能建议仅将此版本用于小型(<100 KiB)文件。在其他情况下,结束执行纯解决方案。但这应该通过性能测试来检查。

对于上的一千行,我得到以下内容:

$ time grep Solaris infile # Solaris is not in the infile

real    0m0.001s
user    0m0.000s
sys     0m0.001s