Bashコードの並列バージョン作成の問題

Bashコードの並列バージョン作成の問題

サンプル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以外のほとんどすべてを使用する必要があります。シェルは一般的にそれほど高速ではなく、Bashは最も遅いものの1つです。また、大規模な配列を処理するときはそれほど効率的ではないと思いますが、特にこれについてのテストを見たことがないと思います。

さらに、スクリプトは外部ループの各反復で 2 つのサブシェルを起動します。 1つはから始まり、(cov_array=()もう1つはコマンド置換から始まります$(IFS=, ; echo "${cov_array[*]}")。 Bash では、子プロセスを分岐する作業が含まれます。適度に行うとそれほど悪くはありませんが、何百万回繰り返すと大きな打撃を受け始めます。

また、各項目の処理に4〜5秒かかる場合、サブプロセスのオーバーヘッドはそれほど重要ではない可能性があります。 (また、16倍の並列化で300万個のアイテムを処理するのに約10日かかり、アイテムあたりわずか数秒かかります。またはアイテムあたり4〜5秒かかります。内部にループの繰り返し?したがって、時間の項目数を掛けますarray1。上記のとおり、3月上旬には7つの項目がありました。最も内側のステップを最適化できるかどうかを検討できます。 )

また、現時点では、スクリプトが有用なコンテンツを印刷しないことに注意してください。割り当てはreg_covサブシェルにあるため、最終的にメインプログラムはこれを見ることができず、出力もありません。複数のタスクを並列に実行するには、複数の異なるプロセスを実行する必要があり、必要に応じて結果をメインプロセスに戻す準備ができている必要があります。少なくともシェルでは自動的には発生しません。あるいは、ファイルから読み取ってファイルとして印刷するだけです。

その後、配列要素をトークン化する比較的マイナーな問題があります。これを${reg2[@]}使用する必要があります。また、実際にはどこでも使用していないので少し奇妙に"${reg2[@]}"見えます。値を直接繰り返すことができるようです。 index 内の要素のみを印刷するか、必要な配列全体を印刷するか、内容全体を印刷します。for i in ${!array1[*]}array1array2echo $cov0covecho "${cov[@]}"echo "${cov[*]}"


タスクの最も内側のステップが実際に実行するタスクとプロジェクトのソースに応じて、reg2GNU Parallelを調べることをお勧めします。ファイルから入力を読み取り、各項目に対してプロセスを実行し、合理的な順序で出力を収集できます。


つまり、シェルで何かを並列化したい場合は、以前の投稿にいくつかの回避策があります。 Bash FORループの並列化

おすすめ記事