高速に並列処理: xargs -P vs parallel -j vs split -n r/

最近になって、 GNU Coreutils の split(1) に --filter ってオプションがあり、入力を並列処理する方法の1つになることを知りました*1

並列処理をしたいときに使うものとして GNU Findutils の xargs(1) -P, --max-procs (おそらく GNU 拡張) と、そのままの GNU parallel があります。サーバ管理で並列にログインしてなんかやる系だともっと多くのバリエーションが有るでしょうが、 synax sugar の域を超えないのでここでは考えません。

尤も split も本来の目的がちょっと違うので同じように比較するのはおかしいんですが、思ったより効率が良いようなので試してみました。

環境は SunOS, Xeon E5-2630 v3 @ 2.40GHz の 2 socket で 32 threads というとこです。

それぞれの実装

  • xargs
    • --max-procs の数まで fork して execvp する実装
ichii386@abby% xargs --version
xargs (GNU findutils) 4.5.14
Copyright (C) 2014 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Written by Eric B. Decker, James Youngman, and Kevin Dalley.
  • parallel
    • 出力の整形や halt-on-error の設定ができるなど、とくに並列 ssh するときの便利スクリプト系な雰囲気です。
    • perl で書かれていて IPC::Open3 を使い SHELL 環境変数を参照する。
ichii386@abby% parallel --version
GNU parallel 20121122
Copyright (C) 2007,2008,2009,2010,2011,2012 Ole Tange and Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
GNU parallel comes with no warranty.

Web site: http://www.gnu.org/software/parallel

When using GNU Parallel for a publication please cite:

O. Tange (2011): GNU Parallel - The Command-Line Power Tool,
;login: The USENIX Magazine, February 2011:42-47.
  • split
    • 一般的な使い方は「ファイルを n 分割する」ことですが、 --filter を指定することで出力ファイルたちを open(3) するのではなく fork して SHELL を execl(3) し pipe を繋げます。
    • それゆえ xargs などと違って「最初に n プロセスだけを起動し、入力を分割して渡す」という方針。
    • -n r/N で入力を N 個の出力に round robin させる (よって入力の終端は起動時には不要)
ichii386@abby% split --version
split (GNU coreutils) 8.23
Copyright (C) 2014 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Written by Torbjorn Granlund and Richard M. Stallman.

ということで、ナイーブには a. を改善しようとして b. or c. になるところ、いやいやプロセス起動が無駄だろ、と思いつつ d. にするのが調整めんどいよね、というときに良いようです。

  • a. 並列度1で1の入力を処理する、を100回やる
  • b. 並列度1で100の入力を処理する、を1回やる
    • 複数の入力を一度に処理できるようにする
  • c. 並列度5で1の入力を処理する、を20回やる
    • xargs 的な方針
  • d. 並列度5で20の入力を処理する、を1回やる
    • split -n r/N --filter の方針

たんに起動するだけ

それぞれの得意分野だけ比較するのもずるいので、以下の条件で統一するようにします。

  • /bin/sh は必ず経由する
  • 入力を引数として何かを実行できる
ichii386@abby% seq 10e3 | ptime xargs -I{} -P32 sh -c '/bin/true {}'

real       10.983304378
user        8.317562721
sys        24.905662776
ichii386@abby% seq 10e3 | SHELL=/bin/sh ptime parallel -k -j 32 /bin/true {}

real       29.879385199
user       21.170876665
sys        53.838705772
ichii386@abby% seq 10e3 | SHELL=/bin/sh ptime split -u -n r/32 --filter 'while read i; do /bin/true $i; done'

real        1.055647177
user        3.119058968
sys        10.104064297

/bin/true が 10e3 回実行されるのは同じですが、こんなに差がでます。ほんとか??

素因数分解ぽい何か

openssl genrsa 32 で作った 2849443549 = 52223 * 54563 が確かにこれで素因数分解できる感を確認する。

ichii386@abby% seq 2 52223 | ptime xargs -I{} -P32 sh -c 'expr 2849443549 % {} != 0 >/dev/null || echo {}'
52223

real     1:00.233418446
user     1:00.874658739
sys      3:06.241644775
ichii386@abby% seq 2 52223 | SHELL=/bin/sh ptime parallel -k -j 32 'expr 2849443549 % {} != 0 >/dev/null || echo {}'
52223

real     2:49.511349011
user     2:08.351005238
sys      5:23.125255516
ichii386@abby% seq 2 52223 | SHELL=/bin/sh ptime split -u -n r/32 --filter 'while read i; do expr 2849443549 % $i != 0 >/dev/null || echo $i; done'
52223

real        5.541663743
user       25.044178700
sys      1:17.303706104

圧倒的すぎて不安になってきます。

まとめ

とにかく圧倒的に split が速いです。正直なんでこんな差がでるのかピンときてません (が、ちゃんと調べてません) 。

parallel なんかは親が重たい図体してるくせに安易に fork してるからってのはあるでしょうけど。なんとなく気軽に xargs 使っているなら考えなおしたほうが良いかも。

(こういう用途で) split に唯一弱点があるとすれば、起動したコマンドが exit -1 した時に異常終了できないことです。

上の素因数分解の例で言えば、xargs の場合 seq inf を入力にしつつ "(echo {}; exit -1)" すれば見つかったところで終了してくれます。それが split だと出来ない。