【问题标题】:How to count number of forked (sub-?)processes如何计算分叉(子?)进程的数量
【发布时间】:2013-07-17 02:39:59
【问题描述】:

其他人编写了(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

脚本在 1000 行输入中查找 $1 需要 20 秒。这段代码分叉了太多的子流程。在实际代码中,使用grepcutawksed 等在每一行上运行更长的管道(例如progA | progB | progC)。

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

澄清一下:

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

【问题讨论】:

    标签: bash process subprocess


    【解决方案1】:

    您可以简单地捕获 SIGCHLD 信号来计算forked 进程。如果您可以编辑脚本文件,那么您可以这样做:

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

    所以fork 变量将包含分叉的数量。最后你可以打印这个值:

    echo $fork FORKS
    

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

    3000 FORKS
    

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

    但这并不准确!这只是调用 shell 完成的分叉。有更多分叉,因为`echo ...|grep...` 分叉启动 来运行此代码。但在它之后也分叉了两次:一次用于echo,一次用于grep。所以实际上它是 3 个forks,而不是一个。所以它是 5000 个叉子,而不是 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)。

    评论

    其实在这种情况下所有forks都可以避免:

    代替

    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)。我可能建议将此版本仅用于小(grap、 过度执行纯 解决方案。但这应该通过性能测试来检查。

    上的一千行我得到以下信息:

    $ time grep Solaris infile # Solaris is not in the infile
    
    real    0m0.001s
    user    0m0.000s
    sys     0m0.001s
    

    【讨论】:

    • 哇,trap "((++fork))" CHLD。在那里真的学到了一些东西。谢谢,祝大家好运。
    • 谢谢,我看到了set -o monitor 的引用并使用了几次。
    • 谢谢! 正是我所希望的!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-15
    • 2013-05-26
    • 1970-01-01
    • 2011-06-24
    相关资源
    最近更新 更多