この記事は「Qiita Advent Calendar 2019 DSLで自作ビルドツールを作ろう」の14日目の記事です。
14日目 並列実行についての検討
Makeには並列実行の機能があります。つまり、make -j32
などと並列数を指定すると、それだけ分のコアを使用してビルド処理を行ってくれる、非常に便利な機能です。これと全く同じことをRumy-Makeで実現しようとすると、ジョブキューを作って指定されたコア数だけジョブを取り出して並列実行するという面倒くさそうな実装になるので、あまりそんなことはやりたくありません。
あまりコア数に気を使う事は無く、今回は単純にRubyの並列実行の機能を使用してジョブの並列化をできないかということを検討しました。
Rubyの並列実行ライブラリ
Rubyの並列化ライブラリを探していると、Parallel Gemというものを見つけました。単純にループ実行を並列化してくれるライブラリのようです。
https://www.xmisao.com/2018/07/22/how-to-use-ruby-parallel-gem.html
これだと、依存関係グラフの依存している複数の子を並列に実行させることができそうです。早速やってみます。
target_older_depends_list = Array.new if target.depend_targets.length == 0 then target_older_depends_list = [true] else target_older_depends_list = Parallel.map(target.depend_targets){|dep| target_older_depends = false skip_do_target = false if $target_list.key?(dep) and $target_list[dep].is_external then # External Target ...
Parallel.map
という機能を使いました。Parallel.map
では、引数として指定したリストに並列にブロックを適用します。まあ極端な話、
target.depend_targets.each{|dep ... }
としていたこれまでの処理を、
target_older_depends_list = Parallel.map(target.depend_targets){|dep| ... }
としただけなのであまり驚きはありません。ただし、そこから先でいろいろと考えなければなりません。これまで、依存するターゲットのタイムスタンプに応じて本ターゲットを実際に実行するかどうかを決めていました。このために、すべての依存するターゲットを回るときに、target_older_depend
というBool値の変数を1つ用意して、1つでも依存ターゲットに本ターゲットよりも新しいものがあれば、target_older_depend
をTrueに設定して本ターゲットを実行していました。しかし、今回はすべての依存ターゲットは並列に実行されるので、変数1つでは足りません。そこで、Parallel.map
は並列に実行した結果をリストで返すという機能を使用して、target_older_depends_list
というリストを用意し、各依存ターゲットと本ターゲットの依存関係をリストで取得することにしました。このリストの中に一つでもTrue(ある依存ターゲットが本ターゲットよりも新しい)を見つければ、本ターゲットは再実行されます。
# 依存ターゲットリストの中に1つでもTrueが存在する、 # つまり依存ターゲットの中に1つでも本ターゲットよりも新しいものが存在した場合、となる。 if target_older_depends_list.include?(true) then # Execute commands! target.commands.each {|command| # puts "#{command}" result = `#{command}` puts result } end
Parallel.map
の中の処理も、Breakしたりcontinueしたりするとおかしな動きをするので、注意深く書き換え、ブロックの最後に依存判定の結果をTrue/Falseで返すようにします。
end ... if not skip_do_target then do_target(dep) end # puts "[DEBUG] : Depends Target \"#{dep}\" : " + target_older_depends.to_s target_older_depends } # Parallel.mapの終わり
これで同様にSwimmer-RISCVのビルドを行います。コア数は4、スレッド数は8で実行を行いました。
RUBYLIB=${HOME}/work/rumy-make/src ./build.rb 84.03s user 53.16s system 533% cpu 25.720 total RUBYLIB=${HOME}/work/rumy-make/src ./build.rb 46.69s user 30.08s system 87% cpu 1:27.64 total