私が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
IFS=,
set -- $foo
echo $2
IFS=$OIFS
ってやれば、誤差の範囲だと思うんですけどねぇ。