![编写 Bash 代码的并行版本时出现问题](https://linux22.com/image/227616/%E7%BC%96%E5%86%99%20Bash%20%E4%BB%A3%E7%A0%81%E7%9A%84%E5%B9%B6%E8%A1%8C%E7%89%88%E6%9C%AC%E6%97%B6%E5%87%BA%E7%8E%B0%E9%97%AE%E9%A2%98.png)
我正在尝试并行化我的示例 Bash 脚本,并尝试了诸如&
和 之类的命令wait
。请告诉我什么是使其并行的有效方法
我当前的代码对于 reg2 变量中的有限条目运行良好。但我在 reg2 变量中有数百万个条目。所以我想让最外层的循环平行。并行化代码后获得相同的输出,即 0,1,2,:,3,4,:,5,6
#!/bin/bash
# array1=$1
# array2=($2)
# reg2=($3)
array1=('bam1' 'bam2' 'bam3' 'bam4' 'bam5' 'bam6' 'bam7')
array2=('cell1' 'cell1' 'cell1' 'cell2' 'cell2' 'cell3' 'cell3')
reg2=('chr1:10484-10572' 'chr1:10589-10632' 'chr1:10636-10661' 'chr1:10665-10690' 'chr1:10694-10719')
start=`date +%s.%N`
l=${#reg2[@]} # number of regions is 30 million on real data
reg_cov=()
j=0
for r in ${reg2[@]}; do
(cov_array=()
old_array2_element=${array2[0]}
for i in ${!array1[*]}; do
new_array2_element=${array2[$i]}
if [[ "$new_array2_element" != "$old_array2_element" ]]; then
cov_array+=(":")
old_array2_element=$new_array2_element
fi
cov_array+=($i) # in actual code this step takes 4-5 seconds to process
sleep 2
done
reg_cov+=($(IFS=, ; echo "${cov_array[*]}")) )
wait
((j++))
echo "$j/$l"
done
#echo ${reg_cov[@]}
cov=()
cov+=(${reg_cov[@]})
echo $cov
end=`date +%s.%N`; runtime=$( echo "$end - $start" | bc -l ); runtime=${runtime%.*}; hours=$((runtime / 3600)); minutes=$(( (runtime % 3600) / 60 )); seconds=$(( (runtime % 3600) % 60 ))
echo "==> completed Runtime: $hours:$minutes:$seconds (hh:mm:ss)"
答案1
正如评论中提到的,由于性能原因,对于数百万个项目,您可能应该使用 Bash 以外的几乎任何东西。 Shell 通常速度不是很快,而 Bash 是最慢的之一。我也不认为它在处理大型数组时会非常有效,但我认为我没有特别见过这方面的测试。
您的脚本还会在外循环的每次迭代中启动两个子 shell,一个从 at 开始,(cov_array=()
另一个在命令替换 中开始$(IFS=, ; echo "${cov_array[*]}")
。在 Bash 中,这些将涉及分叉一个子进程,如果适度的话,这并没有那么糟糕,但重复数百万次,就会开始付出一些代价。
话又说回来,如果每个项目需要 4-5 秒来处理,那么子进程的开销可能就不那么重要了。 (您还需要大约 10 天的时间来处理 3 M 条目,并进行 16 倍并行化,仅每个项目的几秒钟。或者是每个项目 4-5 秒内循环迭代?因此,该时间乘以 中的项目数array1
。如上所述,有 7 件商品,大约是三月初。您可能需要考虑是否可以优化最里面的步骤。)
另请注意,就目前情况而言,您的脚本不会打印任何有用的内容:对的赋值reg_cov
位于子 shell 中,因此最终主程序将看不到它,并且不会有输出。并行运行多个任务还需要运行多个不同的进程,并且如果您需要的话,您必须做出安排将结果移回主进程。它不会自动发生,至少在 shell 中不会。或者只是从文件中读取并打印到文件。
然后还有一些相对较小的问题,${reg2[@]}
会使数组元素进行分词:您应该使用"${reg2[@]}"
它。看起来也for i in ${!array1[*]}
有点奇怪,因为您实际上没有array1
在任何地方使用:在我看来,您可以直接循环遍历值array2
。并且仅打印索引处的echo $cov
元素,要打印整个数组,您需要or打印整个内容。0
cov
echo "${cov[@]}"
echo "${cov[*]}"
根据您的任务最里面的步骤实际执行的操作以及项目的来源reg2
,我建议查看例如 GNU Parallel。它可以从文件中读取输入,为每个项目运行一个进程,并以合理的顺序收集输出。
也就是说,如果您想在 shell 中并行化某些内容,前面的帖子中有一些解决方案,请参阅例如 并行化 Bash FOR 循环
答案2
确实很难看出您想要做什么,但让我们假设您有一个名为 的包含 3000 万行的文件reg2.txt
,并且您想要为每一行运行一个 bash 函数:
doit() {
reg2="$1"
echo do stuff with "$reg2"
array1=('bam1' 'bam2' 'bam3' 'bam4' 'bam5' 'bam6' 'bam7')
for i in ${!array1[*]}; do
printf "$i "
done
echo
}
您应该确保doit chr1:10484-10572
做正确的事情。
当它起作用时,您可以执行以下操作:
export -f doit
cat reg2.txt | parallel doit
这将为doit
reg2.txt 中的每一行运行,每个 CPU 线程并行运行 1 个作业。
答案3
如果我理解正确的话,外层循环大约有 3000 万次迭代,内层循环大约有 7 次迭代,最里面的计算需要 4-5 秒。总共需要 29.9 年才能完成!在最好的情况下,跨 64 个核心并行化这种方法可能会将执行时间减少到约 5.6 个月,但这仍然不切实际。
最好的方法是首先优化 4-5 秒计算的代码(未显示)。