気が向いたら書くやつ

気が向いたら何か書きます

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オプションが便利なようである。

qiita.com

takuya-1st.hatenablog.jp

また、強制終了時などにバックグラウンドのプロセスを確実に停止させるため、trapで終了時の処理を設定しておきたい。

kiririmode.hatenablog.jp

trap "kill $(jobs -p)" EXITあたりが有効らしい。

qiita.com