原始代码。

原始代码。

我有一个脚本,它只是在后台执行另一个进程,试图控制运行的最大进程数(在本例中为 300)。

它最初大约执行脚本。 1-2 毫秒,但运行几个小时后,它最终会以线性斜率减慢到 200 毫秒 - 350 毫秒执行时间。我使用数组来维护 PID#,但也取消设置密钥以减少表大小,但我有一种感觉,这就是罪魁祸首。

#!/bin/bash

threads=()
threadcounter=0

crd=0;

while true; 
do
        threadcounter=${#threads[@]}
        crdcounter=${#crds[@]}

        if [ "$threadcounter" -lt 300 ]
        then
                s1=$(($(date +%s%N)/1000000))
                pidf=$(/opt/remi/php/root/usr/bin/php cli.php initformula $crd >> /tmp/logger) &
                pidfid=$!
                s2=$(($(date +%s%N)/1000000))
                echo "Init " $crd $(expr $s2 - $s1 ) "ms"
                threads[$pidfid]+=$pidfid
        else
            for pid in "${!threads[@]}"; do 
                if [ ! -d "/proc/${pid}" ]
                then
                    unset threads[$pid]
                fi
            done;
        fi;

        if [ "$crd" -gt 9999999 ]
        then
            echo "completed all";
            exit;
        fi;

        crd=$(expr $crd + 1)
done;

答案1

原始代码。

当您开始时,您只需启动 300 份cli.php.这大约需要 1200 个进程,因为您想要测量启动所需的时间。

然后将变量crd从 300 循环到 9999999。

  • 如果 shell 认为threads阵列中有空闲插槽,它将cli.php使用 4 个进程启动一个新插槽。

  • 否则,您将循环大约 300 个进程,让
    内核填充/proc虚拟文件系统,并测试
    目录是否存在。任何丢失的目录都会导致条目
    threads阵列中删除。

您有一个未使用的数组,名为crds.

因为在最初的 300 之后,cli.php如果进程表中有空闲槽,则 crd 变量的每次循环都会创建 1 个新副本,但如果表已满,则最多可以删除 300 个副本,因此在运行结束时我们只知道已启动300 到约 9,967,000 个cli.php进程,进程数量取决于计算机的速度、cli.php执行时间以及计算机上的负载。 6 个数量级需要优化!

经验法则是,在现代机器上,启动 1 个进程在一个核心上需要 1 毫秒,因此在初始启动速率下,您的表现还不错。我预计一旦你用完可用的核心来启动新进程,启动率就会出现显着的变化。

改进

加快速度的一种方法是使用! kill -0 $pid-[ ! -d "/proc/${pid}" ]kill -0杀死任何东西,但如果进程不存在则返回错误。kill是一个 shell 内置函数(原样[),但内核要做的工作量较小。如果大多数时候阵列中没有空闲槽,这将是最有效的threads

expr第二个改进是使用内置算术代替对外部程序的调用$(( ... )),从而减少启动cli.php.如果阵列中大部分时间都有空闲槽,则这是最有效的labels

为了进行更多分析,我们需要知道cli.php运行所需的大致时间以及运行次数。

正如BUGSbash 手册中的部分所述It's too big and too slow.,bash 中的数组实现肯定还有改进的空间。

替代实现

制作

在评论中建议使用xargsparallel。我经常更喜欢使用make.首先要确定cli.php需要多少份副本。然后一个简单Makefile的比如

%:
\t/opt/remi/php/root/usr/bin/php cli.php initformula $@

其中 \t 是制表符。 (这个简单版本假设您没有任何数字名称在 0 到 9999999 范围内的文件)。然后调用 make as

make -O -j 300 $(seq 0 9999999) > /tmp/logger

如果您想要完整的 10,000,000 次 cli.php 调用。我更喜欢make包括xargs在 cli.php 返回错误时不需要采取过多步骤来中止处理的原因。

参数

如需xargs解决方案,请尝试

seq 0 9999999 | xargs -n 1 -P 300 /opt/remi/php/root/usr/bin/php cli.php initformula > /tmp/logger

这更简单。

重击

然而,使用wait -nf并且根本不担心跟踪 PID 的 Bash 解决方案可能更符合 OP 的口味。它启动最初的 300 个进程,然后当它检测到其中一个进程完成时,它将启动另一个进程。一旦第 10,000,000 个作业开始,它就会进行最后的等待,让所有作业完成。不完全相同的算法,但非常接近。

#!/bin/bash
for(crd=0;crd<300;crd++); do
    /opt/remi/php/root/usr/bin/php cli.php initformula $crd & 
done > /tmp/logger
for(;crd<=9999999;crd++); do
    wait -fn
    /opt/remi/php/root/usr/bin/php cli.php initformula $crd &
done >> /tmp/logger
wait

相关内容