2

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

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

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

澄清:

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

1 回答 1

3

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

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

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

echo $fork FORKS

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

3000 FORKS

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

但这并不准确!这只是调用 shell 完成的分叉。有更多的分叉,因为`echo ...|grep...`分叉启动一个来运行这段代码。但在它之后也是两次分叉:一次 forecho和一次 for grep。所以实际上是 3fork秒,而不是 1 秒。所以它是 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; }

第一行读取完整infile 的 tolineArray没有任何循环。第二行是逐个元素处理数组。

措施

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
于 2013-07-18T10:46:56.820 回答