gnu parallel

twitter で一瞬話題になってたのを見かけたのでちょっとだけ使ってみた。

perl だったのでげんなり。 xargs -P と比べれば早くはないけど、 pcntl_fork() なんかに比べればだいぶ早い。

% time seq 1000 | xargs -P4 -I{} echo {} >/dev/null
seq 1000  0.00s user 0.00s system 0% cpu 0.003 total
xargs -P4 -I{} echo {} > /dev/null  0.12s user 0.39s system 268% cpu 0.191 total
% time seq 1000 | parallel -P4 -I{} echo {} >/dev/null
seq 1000  0.00s user 0.00s system 0% cpu 0.003 total
parallel -P4 -I{} echo {} > /dev/null  1.24s user 1.60s system 150% cpu 1.882 total

試行錯誤した限りだと、 {} がないと arg に必ず入っちゃうのかな?

% seq 3 | xargs -I{} sh -c 'set -x; ps h -p $$ -o pid,pgid'
+ ps h -p 4056 -o pid,pgid
 4056  4054
+ ps h -p 4058 -o pid,pgid
 4058  4054
+ ps h -p 4060 -o pid,pgid
 4060  4054
% seq 3 | parallel -I{} 'set -x; ps h -p $$ -o pid,pgid'
+ ps h -p 4193 -o pid,pgid 1
    1     1
 4193  4153
+ ps h -p 4194 -o pid,pgid 2
    2     0
 4194  4153
+ ps h -p 4196 -o pid,pgid 3
    3     0
 4196  4153

ファイル名を便利に扱うための置換マーカみたいなのが最初から入ってる。 {} = aho/baka.dat だとして

  • {.} = aho/baka
  • {/} = baka.dat
  • {/.} = baka

となる。 paralles ssh 的に使うと便利、みたいな例が幾つか上がってて、たしかに DNS 的ホスト名もファイル名も "." で区切るよな、と思った。

なんでちょっといじった。いや、ほら、だいたい 3 つ削ればホスト名になるじゃん。

-- ./opt/src/parallel/parallel-20110322/src/parallel   2011-03-22 09:00:52.000000000 +0900
+++ /home/ichii386/opt/bin/parallel 2011-04-25 21:06:51.000000000 +0900
@@ -391,8 +391,12 @@
     $Global::quoting = 0;
     $Global::replace{'{}'} = '{}';
     $Global::replace{'{.}'} = '{.}';
+    $Global::replace{'{..}'} = '{..}';
+    $Global::replace{'{...}'} = '{...}';
     $Global::replace{'{/}'} = '{/}';
     $Global::replace{'{/.}'} = '{/.}';
+    $Global::replace{'{/..}'} = '{/..}';
+    $Global::replace{'{/...}'} = '{/...}';
     $/="\n";
     $Global::ignore_empty = 0;
     $Global::interactive = 0;
@@ -3092,7 +3096,11 @@
    '{}' => 0, # Total length of all {} replaced with all args
    '{/}' => 0, # Total length of all {/} replaced with all args
    '{.}' => 0, # Total length of all {.} replaced with all args
+   '{..}' => 0, # Total length of all {..} replaced with all args
+   '{...}' => 0, # Total length of all {...} replaced with all args
    '{/.}' => 0, # Total length of all {/.} replaced with all args
+   '{/..}' => 0, # Total length of all {/..} replaced with all args
+   '{/...}' => 0, # Total length of all {/...} replaced with all args
    'no_args' => undef, # Length of command with all replacement args removed
    'context' => undef, # Length of context of an additional arg 
     };  
@@ -3903,11 +3911,27 @@
        # skip
    } elsif($replacement_string eq "{.}") {
        $s =~ s:\.[^/\.]*$::; # Remove .ext from argument
+   } elsif($replacement_string eq "{..}") {
+       $s =~ s:\.[^/\.]*$::; # Remove .ext from argument
+       $s =~ s:\.[^/\.]*$::; # Remove .ext from argument
+   } elsif($replacement_string eq "{...}") {
+       $s =~ s:\.[^/\.]*$::; # Remove .ext from argument
+       $s =~ s:\.[^/\.]*$::; # Remove .ext from argument
+       $s =~ s:\.[^/\.]*$::; # Remove .ext from argument
    } elsif($replacement_string eq "{/}") {
        $s =~ s:^.*/([^/]+)/?$:$1:; # Remove dir from argument. If ending in /, remove final /
    } elsif($replacement_string eq "{/.}") {
        $s =~ s:^.*/([^/]+)/?$:$1:; # Remove dir from argument. If ending in /, remove final /
        $s =~ s:\.[^/\.]*$::; # Remove .ext from argument
+   } elsif($replacement_string eq "{/..}") {
+       $s =~ s:^.*/([^/]+)/?$:$1:; # Remove dir from argument. If ending in /, remove final /
+       $s =~ s:\.[^/\.]*$::; # Remove .ext from argument
+       $s =~ s:\.[^/\.]*$::; # Remove .ext from argument
+   } elsif($replacement_string eq "{/...}") {
+       $s =~ s:^.*/([^/]+)/?$:$1:; # Remove dir from argument. If ending in /, remove final /
+       $s =~ s:\.[^/\.]*$::; # Remove .ext from argument
+       $s =~ s:\.[^/\.]*$::; # Remove .ext from argument
+       $s =~ s:\.[^/\.]*$::; # Remove .ext from argument
    }
    if($Global::JobQueue->quote_args()) {
        $s = ::shell_quote_scalar($s);

ところで、この手のやつで先頭にホスト名入れたい時ってどうします?

% get_host_list | parallel -I{} 'ssh {} "ps -unko -opid,pgrp,nice,etime,stime,stat,args | sort -k4 -nr | head -n3" | sed "s/^/{...}: /"'

とかかな。でも sed って quote とか escape とかめんどいし、パッと見なにしてるかわかんなくてやなんですよね。

あとは 'while read line; do echo "{}: $line"; done' とか? いやー。なんとか yes と paste 組み合わせてなんかできないかと試行錯誤して、諦めました。並列 ssh 専用だったら他に解は沢山あるし。

感想

よくこんなの GNU に入ったね、って思った。なんというか、流儀とかいろいろ。