はじめに
「100種類の学習済みモデルの性能調査をしておいて、テストデータは10種類あるよ」
こう言われた場合、作業の方法は複数ある。
- 1000(100 x 10)回試験を実行する。
- プログラムを書いて自動化し、1回の実行で1000回試験を行えるようにする。
- テストデータを1つに結合して100回試験を行い、後で10種類に戻す。
1はどんなに1回の試験に時間がかからなくてもまず選択しないだろう。プログラムに多少自身があるならおそらく2を選んだのではないか。
2を選んだ理由は「自動化できてるから待ってるだけで終わる」とか「人的ミスが減り、時間がかからないから」などが挙げられるだろう。
しかし、プログラムの作成に1000時間かかっていたとしたら?それなら3(もしくは1)が最も成果を挙げられる方法となる可能性がある。
また、プログラムの作成に時間をかけて、そのプログラムが今回の試験にしか使えないようなシロモノだった場合、最初に選択肢から外した1と時間をかけて頑張りましたという点でほとんど変わらない(プログラミングの勉強になったとかはあるかもしれない)。
本記事では、この問題を解決することができるGNU Parallelについて紹介する。
GNU Parallel
本家サイトからの引用。
GNU parallel is a shell tool for executing jobs in parallel using one or more computers. A job can be a single command or a small script that has to be run for each of the lines in the input. The typical input is a list of files, a list of hosts, a list of users, a list of URLs, or a list of tables. A job can also be a command that reads from a pipe. GNU parallel can then split the input and pipe it into commands in parallel.
GNU パラレルは、1つ以上のコンピューターを使用してジョブを並列に実行するためのシェルツールです。ジョブは、単一のコマンドにすることも、入力の各行に対して実行する必要がある小さなスクリプトにすることもできます。典型的な入力は、ファイルのリスト、ホストのリスト、ユーザーのリスト、URLのリスト、またはテーブルのリストです。ジョブは、パイプから読み取るコマンドにすることもできます。その後、GNU Parallelは入力を分割し、それを並列にコマンドにパイプすることができます。
詳しい使い方はDocumentationに載っているpdfや動画を見れば分かるので、詳しく知りたくなったらそちらを参考にしていただきたい。
本記事では、仕事や研究で使うことを想定して、GNU Parallelの使い方を紹介する。
どんな場面で使えるのかについては、筆者の想定している使い方以外にもあると思うのであくまで1つの例である。
使用例
まずはインストールから
$ sudo apt-get install parallel
ウォーミングアップ
$ parallel echo ::: hello world !
hello
world
!
$ parallel echo {1} {2} ::: 5 4 ::: 3 2 1
5 3
5 2
5 1
4 3
4 2
4 1
# testディレクトリ内のすべてのファイルをcopy_textディレクトリにコピー
$ ls text/* | parallel "cp {} copy_text/"
フォルダ内のすべてのテキストファイルに対してpythonスクリプトを実行する
$ ls text/* | parallel "python sample01.py {} > resulit/{/}
ディレクトリ読み込みの処理を書くことなく、ファイル読み込みのスクリプトで同じ動作ができる。
複数のパラメータですべての場合を調査する
$ MODEL="ls model/*"
$ DATA="ls text/*"
$ parallel "python sample02.py {1} {2} > resulit/{1/}" ::: ${MODEL[@]} ::: ${DATA[@]}
もしかすると別の方法でも出来るのかもしれないが、並列化で高速に実行できるのが強み。
巨大なテキストファイルを複数行に分けてpythonスクリプトを実行する
$ cat text/sample03.txt | parallel --pipe --L 10000 "python sample03.py" > resulit/result.txt
巨大なテキストで処理に時間がかかるなと思ったときに、こちらの方法を使うことで高速に実行できる。
引数やオプションについて
{}
# 置換文字列。標準入力が挿入される。ディレクトリは省略されない。
{.}
# 標準入力の拡張子部分を削除
{/}
# 標準入力のディレクトリ部分を削除
{//}
# ディレクトリ部分が挿入
{1}
# 入力ソース1または1番目の引数
{1/}{1.}{1/.}
# 上記コマンドの組み合わせ
-j N
# CPU数。デフォルトではすべてのCPUを使用する。
--bar
# 進捗具合を表示
[-d]--delay mytime
# 次のジョブの開始を遅らせる。単位は秒だが、以下のような書き方も可能
$ parallel -d 1d3.5h16.6m4s echo {1} {2} ::: 5 4 ::: 3 2 1
[-k]--keep-order
# 入力の順序と出力の順序を統一する。デフォルトではジョブが終わった順で出力されている。
--dry-run
# 実際に実行されるコマンドを出力。実行はされないのでコマンドがあっているかの確認用。
--pipe N
# ジョブで標準入力を分散する。単位は行。
おわりに
作業を自動化したからといって、自動化環境の構築に時間を取られすぎてしまっては本末転倒である。もちろん長い目で見れば十分に価値はあるという場合は別だが。
テクニカルなプログラムの書き方だけでなく、既存の外部ツールなどに目を向けると、もっと汎用的なプログラムを書くだけで目的を実現できることもある。