我有一个脚本,它只是在后台执行另一个进程,试图控制运行的最大进程数(在本例中为 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
运行所需的大致时间以及运行次数。
正如BUGS
bash 手册中的部分所述It's too big and too slow.
,bash 中的数组实现肯定还有改进的空间。
替代实现
制作
在评论中建议使用xargs
或parallel
。我经常更喜欢使用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