ruby で同時処理のスレッド制御 でスレッド制御しながら、キューを使わずコマンドを並列実行してきた。
よく考えたら、こんな車輪の再発明などしなくても先人の知恵を借りたほうが楽そう。
調べてみたらxargsに同時実行のオプションがあるのを確認した。
max-procsのこのオプションを知って、せっかく覚えたての find -exec 投げ捨てたくなった
xargs 最強じゃないですか。
xargs にコマンドを並列実行する方法がある。
-P/ --max-procs オプション
--max-procs=max-procs -P max-procs Run up to max-procs processes at a time; the default is 1. If max-procs is 0, xargs will run as many processes as possible at a time. Use the -n option with -P; otherwise chances are that only one exec will be done.
(man ページより)
並列で早くなるのか次の実験をしてみました
test.pngをtest.jpg に変換する作業を繰り返してみました。
test.png を 1000枚 jpg に変換する
作ったJPEG画像 1000枚 を削除する
takuya@air:~/Desktop$ time for i in {1..1000}; do echo $i.jpg ; done | sort | xargs -I{} convert test.png {} real 0m26.534s user 0m15.756s sys 0m6.532s
次に削除を測定。
takuya@air:~/Desktop$ time for i in {1..1000}; do echo $i.jpg ; done | sort | xargs -I{} rm {} real 0m4.271s user 0m1.278s sys 0m1.830s
並列度10で、並列実行してみる。
PNG→JPEGの変換
1個ずつの時は、26秒だったが・・・
takuya@air:~/Desktop$ time for i in {1..1000}; do echo $i.jpg ; done | sort | xargs -P10 -I{} convert test.png {} real 0m7.555s user 0m18.874s sys 0m7.169s
JPG画像1000枚の削除
削除1ずつ起動すると、4 秒だったが・・・
takuya@air:~/Desktop$ time for i in {1..1000}; do echo $i.jpg ; done | sort | xargs -P10 -I{} rm {} real 0m1.818s user 0m1.711s sys 0m2.408s
というわけで、ディスクのIOコストやCPUのコストを超えない限りは限界まで早くなりそうです。
並列度を色々変えて実験してみました。
10までで充分早くなったので満足でした。せっかくなので、並列度を色々変えて時間を測ってみました。
並列度2の場合
#作成 takuya@air:~/Desktop$ time for i in {1..1000}; do echo $i.jpg ; done | sort | xargs -P2 -I{} convert test.png {} real 0m16.761s user 0m18.541s sys 0m7.461s #削除 takuya@air:~/Desktop$ time for i in {1..1000}; do echo $i.jpg ; done | sort | xargs -P2 -I{} rm {} real 0m2.409s user 0m1.384s sys 0m1.973s
並列度4の場合
#作成 takuya@air:~/Desktop$ time for i in {1..1000}; do echo $i.jpg ; done | sort | xargs -P4 -I{} convert test.png {} real 0m10.660s user 0m18.978s sys 0m7.364s #削除 takuya@air:~/Desktop$ time for i in {1..1000}; do echo $i.jpg ; done | sort | xargs -P4 -I{} rm {} real 0m1.850s user 0m1.685s sys 0m2.330s
並列度100の場合
100までくると、さすがに起動コストやメモリのスワップが発生スルだろうと思っテストしてみた。
やっぱり遅くなった。一方で削除のような簡単な処理は、影響が少なさそう
#作成 takuya@air:~/Desktop$ time for i in {1..1000}; do echo $i.jpg ; done | sort | xargs -P100 -I{} convert test.png {} real 0m9.006s user 0m19.025s sys 0m7.514s #削除 takuya@air:~/Desktop$ time for i in {1..1000}; do echo $i.jpg ; done | sort | xargs -P100 -I{} rm {} real 0m1.681s user 0m1.704s sys 0m2.363s
***並列度1000の時
#作成 takuya@air:~/Desktop$ time for i in {1..1000}; do echo $i.jpg ; done | sort | xargs -P1000 -I{} convert test.png {} real 0m8.841s user 0m18.919s sys 0m7.318s #削除 takuya@air:~/Desktop$ time for i in {1..1000}; do echo $i.jpg ; done | sort | xargs -P1000 -I{} rm {} real 0m1.788s user 0m1.707s sys 0m2.388s
処理1000に対して、並列度1000.つまり & を着けて起動したのと同じことですね。
どうもうちのマシンだと、7秒が上限のようです。別のボトルネックにアタったようです。
並列度10000の時
並列度をもっとあげてみたり。
takuya@air:~/Desktop$ time for i in {1..1000}; do echo $i.jpg ; done | sort | xargs -P10000 -I{} convert test.png {} real 0m9.127s user 0m19.045s sys 0m7.499s takuya@air:~/Desktop$ time for i in {1..1000}; do echo $i.jpg ; done | sort | xargs -P10000 -I{} rm {} real 0m1.793s user 0m1.705s sys 0m2.371s
まとめ
並列度 | PNG→JPEGに1000枚を変換 | 1000枚のJPEG画像を削除 | |
なし(何もしない) | 0m26.534s | 0m4.271s | |
2 | 0 m16.761s | 0m2.409s | |
4 | 0m10.660s | 0m1.850s | |
10 | 0m7.555s | 0m4.271s | |
100 | 0m9.006s | 0m1.681s | |
1000 | 0m8.841s | 0m1.793s |
どうやら、私のマシン限界は今回の実験環境だと10s弱程度のようでした。
単純な起動だけ26secよりは断然早くなったのでOKです。
ディスクIOで詰まりそうなので、real で比較 。
ちなみに、単純なCPU起動時間だけのSYSだと並列度で結果が殆ど変わらないので、IOコストが限界を迎えていることも想像がつく。
1つずつ起動するより、ファイルに書き出している最中にコマンドを起動したほうが時間節約にはなるってことですね。
実験をしたマシン
サンプル画像
おまけ
ギリギリまで早くする -P0 オプション。
パフォーマンスチューニングなんて測定したり面倒だぜ!って人には xargsにお任せってオプションが有ります。
試してみた。
作成
takuya@air:~/Desktop$ time for i in {1..1000}; do echo $i.jpg ; done | sort | xargs -P0 -I{} convert test.png {} real 0m8.294s user 0m18.902s sys 0m7.349s
削除
takuya@air:~/Desktop$ time for i in {1..1000}; do echo $i.jpg ; done | sort | xargs -P0 -I{} rm {} real 0m1.895s user 0m1.709s sys 0m2.418s
たしかに、測定値のギリギリ上限に近いです。
自動的に上限に近づけてくれるなら楽ちんですね。
-n : さらに早くなるかもしれないオプション
上記の例だとrm コマンドは単純に1000回起動してるわけで、 rm コマンドに一度にもっと引数を渡せば、起動回数が少なくて済む。というメリットも有る。