bashでの簡易な並列処理
動機
業務のなかで、ツール実行中のメモリフットプリントを測定する必要が出てきた。
簡易的な計測でいいというだったので、「測定対象のツールと、ps
などのコマンドを並列実行してメモリ使用量を計測する」という方法を思いつき、シェルスクリプトで並列処理をする方法について調べた。
バックグラウンド実行による並列処理
所望の処理を&
でバックグラウンドジョブとして実行し、wait
で終了を待機する(プロセスIDを指定しない場合、すべてのバックグラウンドジョブを待つ)。
次の例では、それぞれ異なる回数のecho
を実行する2つの処理を並列実行させ、同期をとっている。
#!/bin/bash # >>>>>> # para.sh # >>>>>> # 処理1 proc1() { for i in $(seq 5); do echo "proc1-$i" done } # 処理2 proc2() { for i in $(seq 10); do echo "proc2-$i" done } proc1 & proc2 & wait
実行結果
$ ./para.sh proc1-1 proc1-2 proc2-1 proc1-3 proc2-2 proc1-4 proc2-3 proc1-5 proc2-4 proc2-5 proc2-6 proc2-7 proc2-8 proc2-9 proc2-10
期待通り、処理1と処理2が並列に実行されている。
無限ループを含む並列実行
前掲の手法は処理の終了待ちが起こるため、当初の目的であるプロセスの監視などには向かない。
次の例では、メモリ使用量測定のために、ps
をバックグラウンドで無限ループさせている。
$!
でプロセスIDを取得しておき、対象の処理が終わったのち測定処理を終了させる、というかたちを取っている。
#!/bin/bash # >>>>>>>>> # para2.sh # >>>>>>>>> # 計測プロセス # このシェルスクリプトのメモリ使用量を1秒ごとに表示する free_per_1s() { # ログのヘッダ部 ps aux | head -1 while true; do # $$はこのシェルのプロセスID # grep -v grep はgrep自身が表示されるのを防ぐための処理 ps aux | grep $$ | grep -v grep sleep 1 done } # 時間のかかる処理: 配列へ値を大量に追加する some_long_proc() { array=() for i in $(seq 100000); do array+=$i done } # $!で直前のバックグラウンドジョブのプロセスIDを取得する free_per_1s & pid=$! some_long_proc # 計測プロセスを終了する kill -TERM ${pid}
実行結果
$ ./para2.sh USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND ubuntu 11683 0.0 0.0 18304 4716 tty1 R 01:27 0:00 /bin/bash ./para2.sh ubuntu 11683 54.0 0.0 25432 11748 tty1 R 01:27 0:01 /bin/bash ./para2.sh ubuntu 11683 70.6 0.0 25432 11824 tty1 R 01:27 0:02 /bin/bash ./para2.sh ubuntu 11683 78.7 0.0 25780 12156 tty1 R 01:27 0:03 /bin/bash ./para2.sh ubuntu 11683 83.6 0.0 25912 12248 tty1 R 01:27 0:04 /bin/bash ./para2.sh ubuntu 11683 86.8 0.0 25912 12324 tty1 R 01:27 0:05 /bin/bash ./para2.sh ubuntu 11683 89.1 0.0 26044 12396 tty1 R 01:27 0:06 /bin/bash ./para2.sh ubuntu 11683 90.8 0.0 26044 12460 tty1 R 01:27 0:07 /bin/bash ./para2.sh ubuntu 11683 92.2 0.0 26176 12520 tty1 R 01:27 0:08 /bin/bash ./para2.sh
というわけで、あるプロセスのメモリ使用量を一つのスクリプトで測定することができた。
参考
同一のプロセスを並列実行するなら、xargs
の-P -mac-procs
オプションが便利なようである。
また、強制終了時などにバックグラウンドのプロセスを確実に停止させるため、trap
で終了時の処理を設定しておきたい。
trap "kill $(jobs -p)" EXIT
あたりが有効らしい。