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ループの並列化

答え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

これはreg2.txtの各行に対して実行され、doitCPUスレッドごとに1つのジョブを並列に実行します。

答え3

私が正しく理解した場合、外側のループは約3000万回の反復を持ち、内側のループは約7回の反復を持ち、最も内側の計算には4〜5秒かかります。完成するのに合計29.9年かかります!最善のケースでは、64コアにわたってこのアプローチを並列化すると、実行時間が約5.6ヶ月に短縮される可能性がありますが、まだ非現実的です。

最善のアプローチは、最初に4〜5秒の計算のためにコードを最適化することです(表示されていません)。

関連情報