私がawkを弁護するのもなんだけど。

hogehoge - やっぱりawkは遅い
シェルでよくあるんだけど、ある区切りの○番目を取ってきたい、っていう場面で
foo="aaaa,bbbb,cccc"
bar=`echo $foo | awk -F, '{print $2}'`
ってやる時がある。 けど、これ、むちゃくちゃ無駄。一回しか実行されないなら大したボトルネックにはならないが、ループの中で使うと酷い事になりがち。

遅いのはawkでなくてfork(2)だと思う。

以下、10を1000に書き換えて、MacBook Pro (Core Duo 2GHz)で実行してみた結果。

% time ./builtin.sh 
0.138u 0.504s 0:00.82 76.8%     0+0k 0+8io 0pf+0w
% time ./awk.sh 
1.380u 4.739s 0:06.27 97.4%     0+0k 0+2io 0pf+0w
#!/bin/sh

typeset -i i=0

while (( i < 1000 ))
do
        echo "aaaa,bbbb,cccc" | perl -F, -anle  'print $F[1]' > /dev/null
        i=$(( i + 1 ))
done
% time ./perl.sh 
1.991u 5.257s 0:07.40 97.8%     0+0k 0+1io 0pf+0w

そう。awkの方がperlよりも軽い。しかしここでperlが遅いと早合点してはいけない。perlの方がbinaryが大きい分execに時間がかかるのはある意味当然。その代わり、一端起動したら速い。

#!/usr/bin/env perl
use strict;
use warnings;
my $str   = "aaa,bbb,ccc";
open my $null, '>', '/dev/null';
for (1..1000){
  my @words = split /,/, $str;
  print {$null} "$words[1]\n" 
}
% time ./builtin.pl
0.008u 0.006s 0:00.01 0.0%      0+0k 0+1io 0pf+0w

ついでにruby。

#!/usr/bin/env ruby
str   = "aaa,bbb,ccc"

open("/dev/null", "w") do |f|
  1000.times do
    f.puts str.split(/,/)[1]
  end
end
% time ./builtin.rb
0.010u 0.005s 0:00.01 100.0%    0+0k 0+0io 0pf+0w

結論としては、

  • shにしろperlにしろ、外部コマンドに処理をまかせるのは重い
  • 同じ内部コマンドだけで処理するのであれば、shよりもperlやrubyの方がずっと速い(多分pythonも)
  • さらにプログラムもずっと理解しやすい。

「理解しやすい」は多分に主観的ではあるが、少なくともi=$(( i + 1 ))のようなギミックは不要だ。

shell scriptハッカーを私は尊敬するが、21世紀の今からそれを目指すのはあまりお薦めできない。shellはあくまで「他力本願」に最適化されているのだし(/bin/[だって外部コマンド!)、shellだけで完結させるshell scriptはサディスティックなhackに思えてならない。

そうそう。もちろんgolferは別。

Dan the Man with Too Many Ways to Do It