シェルスクリプトの高速化
シェルスクリプトの高速化
業務でシェルスクリプトの高速化を追求する必要が出てきたので、整理。
シェルスクリプトノウハウ - モノノフ日記
ShellScript - shellで書かれたbatch scriptを手軽に高速化する - Qiita
ループ文のリダイレクト、パイプとバックグランドでの実行
Linuxコマンド集 - 【 wait 】 プロセスおよびジョブの終了を待つ:ITpro
②多重ループによる無駄な処理を回避すること
状況にもよるが多重ループが必ずしも早いとは限らない。
例えば毎行に対する処理を行う場合、単純にfor文で回して生成したカウンタを使って処理を実施するよりも、読み込んだ行に対してのみ処理を行いカウンタを使用して自分でカウントアップする方が早くなることもある。
③無関係な処理同士は極力バックグラウンドで
無関係な処理は立て続けにやる必要がないので並列処理にする。
なお、waitコマンドはすべてのバックグラウンドで実行しているプロセスの終了を待ってくれる。
これだけで大幅に高速化できた。
実際の確認
下記に実際のソースを記載する。
まず、3つのフィールドを持つcsv形式のランダムなファイルをテスト用に作成するためのプログラム
テスト用の入力ファイルを事前に作成するためのプログラム。
入力ファイルの定義条件を以下とする。
・第1フィールド : +[A〜I].
・第2フィールド : +[1〜4].
・第3フィールド : 0、または+[1〜999]
・上記の全ての組み合わせパターンでデータ(行)を作成し、行はランダムにソートする
【プログラム】
#!/bin/sh F1_LINE=",+A.,+H.,+D.,+G.,+C.,+E.,+I.,+B.,+F.," cp /dev/null inFile.csv.tmp for f1 in `echo $F1_LINE | sed -e "s/,/ /g"` do for f2 in `seq 1 4` do for f3 in `seq 0 999` do if [ "$f3" != "0" ] then f3="+$f3." fi echo "$f1,+$f2.,$f3," >> inFile.csv.tmp done done done cat inFile.csv.tmp | while read x; do echo -e "$RANDOM\t$x"; done | sort -k1,1n | cut -f 2- > inFile.csv rm -f inFile.csv.tmp exit 0
【生成されるファイルinFile.txtの内容(例)】※ファイルの並びはランダム
$ cat inFile.txt +A.,+2.,+602., +B.,+1.,+571., +B.,+2.,+706., ・ ・ ・ +E.,+4.,+873., +D.,+4.,+590., +A.,+3.,+763., $
上記を踏まえて、下記のルールでファイルを書き直す。
・第3フィールドを一意な値になるように振りなおす。
・ただし、元々第1フィールドと第3フィールドが同じ値であったものには全て同じ値が振られるようにする。
多重ループで立て続けに処理を行う場合
【プログラム】
$ cat loopTest.sh #!/bin/sh F1_LINE=",+A.,+H.,+D.,+G.,+C.,+E.,+I.,+B.,+F.," cp /dev/null outFile.csv CNT=0 for f1 in `echo $F1_LINE | sed -e "s/,/ /g"` do /bin/echo -n "." for f3 in `seq 0 339` do if [ "$f3" != "0" ] then f3="+$f3." fi cp /dev/null TMP_TEST_RONDOM.csv cat inFile.csv | grep ^"$f1,+[1-4].,$f3," > TMP_TEST.csv while read line do NEW_F3=$CNT if [ "$NEW_F3" != "0" ] then NEW_F3="+$NEW_F3." fi echo $line | awk -F, -v t="$NEW_F3" 'BEGIN{OFS=","} END{$1=$1;$3=t;print}' >> TMP_TEST_RONDOM.csv done < TMP_TEST.csv sort TMP_TEST_RONDOM.csv -t, -k 2 >> outFile.csv CNT=$((CNT + 1)) done done rm -f TMP_TEST.csv rm -f TMP_TEST_RONDOM.csv echo "" cp /dev/null outFile2.csv CNT=0 for f1 in `echo $F1_LINE | sed -e "s/,/ /g"` do /bin/echo -n "." for f3 in `seq 0 339` do if [ "$f3" != "0" ] then f3="+$f3." fi cp /dev/null TMP_TEST_RONDOM.csv cat inFile.csv | grep ^"$f1,+[1-4].,$f3," > TMP_TEST.csv while read line do NEW_F3=$CNT if [ "$NEW_F3" != "0" ] then NEW_F3="+$NEW_F3." fi echo $line | awk -F, -v t="$NEW_F3" 'BEGIN{OFS=","} END{$1=$1;$3=t;print}' >> TMP_TEST_RONDOM.csv done < TMP_TEST.csv sort TMP_TEST_RONDOM.csv -t, -k 2 >> outFile2.csv CNT=$((CNT + 1)) done done rm -f TMP_TEST.csv rm -f TMP_TEST_RONDOM.csv echo "" exit 0 $
【実行結果】
$ time ./loopTest.sh
.........
.........
real 19m8.678s
user 13m12.480s
sys 5m54.943s
$
読み込んだ行に対してのみ処理を行いカウンタを使用して自分でカウントアップする場合
【プログラム】
$ cat loopTest_speed_multi.sh #!/bin/sh F1_LINE=",+A.,+H.,+D.,+G.,+C.,+E.,+I.,+B.,+F.," cp /dev/null outFile_speed_multi.csv CNT=-1 FIELD3="" for f1 in `echo $F1_LINE | sed -e "s/,/ /g"` do /bin/echo -n "." grep ^"$f1" inFile.csv | sort -t, -k 3,3 > TMP_TEST2.csv while read line do if [ "$(echo $line | awk -F, '{print $3}')" != "$FIELD3" ] then CNT=$((CNT + 1)) FIELD3="$(echo $line | awk -F, '{print $3}')" fi NEW_F3=$CNT if [ "$NEW_F3" != "0" ] then NEW_F3="+$NEW_F3." fi echo $line | awk -F, -v "t=$NEW_F3" 'BEGIN {OFS=","} END{$1=$1;$3=t;print}' >> outFile_speed_multi.csv done < TMP_TEST2.csv done & rm -f TMP_TEST2.csv cp /dev/null outFile_speed2_multi.csv CNT=-1 FIELD3="" for f1 in `echo $F1_LINE | sed -e "s/,/ /g"` do /bin/echo -n "." grep ^"$f1" inFile.csv | sort -t, -k 3,3 > TMP_TEST2_multi.csv while read line do if [ "$(echo $line | awk -F, '{print $3}')" != "$FIELD3" ] then CNT=$((CNT + 1)) FIELD3="$(echo $line | awk -F, '{print $3}')" fi NEW_F3=$CNT if [ "$NEW_F3" != "0" ] then NEW_F3="+$NEW_F3." fi echo $line | awk -F, -v "t=$NEW_F3" 'BEGIN {OFS=","} END{$1=$1;$3=t;print}' >> outFile_speed2_multi.csv done < TMP_TEST2_multi.csv done & rm -f TMP_TEST2_multi.csv echo "" wait exit 0 $
【実行結果】
$ time ./loopTest_speed_multi.sh
.
.................
real 8m8.625s
user 5m56.679s
sys 8m40.820s
$
多重ループの方が速い場合とは
例えば、前記した「多重ループで立て続けに処理を行う場合」を「読み込んだ行に対してのみ処理を行いカウンタを使用して自分でカウントアップする場合」と同様にバックグラウンドにして条件を揃えたとする。
これでも「読み込んだ行に対してのみ処理を行いカウンタを使用して自分でカウントアップする場合」の方が速い。
しかし、もし入力ファイルの第3フィールドが0〜999ではなく0〜339であり、それに合わせてプログラムのループ回数も0〜339にした場合、「多重ループで立て続けに処理を行う場合」と「読み込んだ行に対してのみ処理を行いカウンタを使用して自分でカウントアップする場合」の実行時間は同じになる。
また、例えば0〜199など、339よりもっとデータ数が少ないと逆に「多重ループで立て続けに処理を行う場合」の方が処理終了までの時間は早くなる。
データ量に合わせて、多重ループで回すか、ファイルの読み込み行分だけ処理を行うか、処理を比較して適宜判断していくのがよさそうだ。