Tomitomi's blog

勉強したこと、やってみたことなどを書き連ねていく場

makeコマンドで複数のコマンドをいい感じに同時実行する.

 実行に時間がかかる複数のコマンドを実行するときに, 同時に走るコマンドの数をよしなに調整してほしいと思ったことってありませんか? 自分は研究でパラメータが違う複数のシミュレーションを同時に回すことがあります. そういう時,できるだけシミュレーションに時間を取りたくないので, CPUのスレッドをすべて使い切るようにして最大の効率でシミュレーションを回したいです. 以前に学会で発表するデータを取ったときは,この作業を手動でやっていました. 正直めんどくさいですし,自動化したいです.

makeだったらやってくれるのでは?

 いい感じに常にCPUのスレッド数分のジョブを回してくれるものはないものかと, なんなら自分でそう言ったツールを作ろうとすら考えていた時に,一つ思いつきました.

 makeだったらいい感じにやってくれるのでは? makeは,もともとソフトウェアの依存関係を解決して必要なものから順にビルドしてくれるツールです. そして,-jオプションをつけることで,ビルドをできるだけ並行してやってくれます.*1 *2 さらに,-jの後に数字を入れることで,同時に実行するジョブの最大数を指定できます. ということは,同時に実行したいコマンドをmakeが同時に実行するようにして,-j (同時に実行する数)として実行すれば,最初の目的が達成できるのではないかと思いました.

 あらかじめ断っておきますが,これはmakeの本来の使い方からは外れています. makeの動作を使った小技のつもりで書いていることをご了承ください.

試してみた.

 makefileをこんな風にしてみます.

all: 1 2 3 4 5 6 7 8 9 10

1:
    echo 1;sleep 1
2:
    echo 2;sleep 1
3:
    echo 3;sleep 1
4:
    echo 4;sleep 1
5:
    echo 5;sleep 1
6:
    echo 6;sleep 1
7:
    echo 7;sleep 1
8:
    echo 8;sleep 1
9:
    echo 9;sleep 1
10:
    echo 10;sleep 1

詳しく見ていくと,1行目にはallをビルドするには,1~10をビルドする必要があるということが書かれています. 以降,1~10のビルド方法が記述されているのですが,そこにはecho (番号);sleep 1と書かれています. このmakefileでは,実際にはソフトウェアがビルドされるわけではなく,1~10までの数字を1秒ずつかけて表示します. 1~10は,ほかに依存しているものはないので,同時に実行しようとすれば同時に実行されます.

このmakefileを使って,本当に同時に実行できるかどうか試してみます.

まずは,下のコマンドで実行します.

make

すると,1秒ごとに数字が出てきて,10秒で実行終了します.

echo 1;sleep 1
1
echo 2;sleep 1
2
echo 3;sleep 1
3
echo 4;sleep 1
4
echo 5;sleep 1
5
echo 6;sleep 1
6
echo 7;sleep 1
7
echo 8;sleep 1
8
echo 9;sleep 1
9
echo 10;sleep 1
10

これから,特にオプションをつけずに実行すると,ひとつずつ実行されることがわかります.

次に,下のコマンドで実行します.

make -j 5

すると,今度は同時に数字が出力されて,実行時間は2秒で終了します.

echo 1;sleep 1
echo 2;sleep 1
echo 3;sleep 1
echo 4;sleep 1
echo 5;sleep 1
1
2
3
4
5
echo 6;sleep 1
echo 7;sleep 1
echo 8;sleep 1
echo 9;sleep 1
6
echo 10;sleep 1
7
8
9
10

確かに,同時に5つのジョブが並行して動いたようですね. これで,echo~の代わりに同時に実行したいコマンドを入れればうまくやってくれそうです.

もう少し使いやすくしたい!

 しかし,こんなmakefileを手動で書くのは面倒ですよね. 今回は10個でしたが,状況によっては100個とか1000個とかさばく必要があると思います. そうなるともはや手入力するのも馬鹿馬鹿しいですよね. ということで,このmakefileを自動生成するスクリプトを書きました.

#!/bin/bash

echo "" > _makefile
COUNTER=1

function jobs_add(){
    echo "$COUNTER:" >> _makefile
    echo -e "\t$@" >> _makefile
    COUNTER=$(( $COUNTER + 1 ))
}

#======ここにコマンド追加============
for i in $(seq 1 10)
do
    jobs_add "echo $i;sleep 1"
done
#====================================
echo "all: $( seq -s ' ' 1 $(( $COUNTER - 1)) )" > makefile
cat _makefile >> makefile

このコードのここにコマンド追加以下の部分でjobs_add "コマンド"を実行すると,そのコマンドを実行するジョブが1つ追加されます. そして一番下の2行でmakefileを生成します. このコードを実行すると,最初のmakefileが生成されます.

実用的にはさらに自動実行するようにすると,このスクリプトを実行するだけでジョブが回せるので便利だと思います.

まとめ

 複数のジョブを常にCPUのスレッド数いっぱいになるように維持するのは, 手動では大変ですが,makeの-jオプションを使うことで,いい感じにやってくれることが分かりました. このジョブを回すためのmakefileは手動で作るのが大変なので,自動生成するスクリプトを書きました.

今後も,こんな感じで自動化できそうな面倒ごとをちまちま自動化していきたいと思います.