tkuchikiの日記

新ブログ https://blog.tkuchiki.net

Shellscript で指定回数ループするコードのベンチマーク

Shellscript で 指定回数ループする場合を調べていたら、
while でインクリメントしていくか、for in seq の2 パターンが見つかった。
どちらが高速なのか疑問に思ったのでベンチマークを取った。

while

code

#!/bin/sh
i=0
while [ $i -ne 10000 ]; do
  i=`expr $i + 1`
  echo $i
done

result

$ time ./while.sh
real    0m20.557s
user    0m4.466s
sys     0m10.371s

for

code

#!/bin/sh

for i in `seq 1 10000`; do
  echo $i;
done

result

$ time ./for.sh
real    0m0.188s
user    0m0.132s
sys     0m0.041s

考察

while が圧倒的に遅いことがわかった。
この結果から、while で行っている変数代入、expr、while 自体、のいずれかが遅いと考えられる。
そこで、この3つのどこに原因があるのか調査した。

while 自体が遅い?

これはほとんどないと思われるが一応ベンチをとる。

code

seq の結果をパイプで渡して、read すれば for in seq と大体同等の処理になると思われる(多分)。

seq 1 10000 | while read i; do
  echo $i
done
result
$ time ./seq_while.sh
real    0m0.239s
user    0m0.184s
sys     0m0.061s

read の分で for より少し遅くなっていると思われるが、誤差の範囲内である。

変数代入

code

seq_while.sh に 代入を加えただけ。

seq 1 10000 | while read i; do
  hoge=$i
  echo $i
done
result
$ time ./assign.sh
real    0m0.326s
user    0m0.250s
sys     0m0.092s

seq_while.sh より少し遅くなったくらいで、これも原因ではなさそうだ。

expr

消去法で、これが原因だとわかる。

結論

expr は遅いので、指定回数ループを回すときには使わないほうが良い。
また、expr 自体コストが高いようなので四則演算を頻繁に行う場合は、perl などに行わせたほうが良いのだと思う。

dstat で expr のループを見ていたがそこそこ CPU を喰っていたので、CPU リソースの観点からも使わないほうが良いと思われる。

おまけ

以下のような perl script もベンチをとってみた。

code

for my $i (1..10000) {
  print $i + 1, "\n";
}

result

$ time perl ./loop.pl
real    0m0.044s
user    0m0.017s
sys     0m0.026s

四則演算しても、shellscript より perl のほうが速い。

2014/04/08 追記

id:hirata_yasuyuki さんにコメントいただいた、$(()) (以下、算術式展開)のベンチマークもとってみました。
id:hirata_yasuyuki さん、ありがとうございます。
以前の環境が思い出せないので、expr と for seq を再度実行して、結果の比較を行った。

expr

code

while.sh と同じ

result

real    0m17.375s
user    0m4.588s
sys     0m8.534s

for seq

code

for.sh と同じ

result

real    0m0.207s
user    0m0.154s
sys     0m0.028s

算術式展開

code

#!/bin/sh
i=0
while [ $i -ne 10000 ]; do
  i=$(($i+1))
  echo $i
done

result

real    0m0.291s
user    0m0.254s
sys     0m0.035s

結論

ご指摘いただいた通り、算術式展開を使ったほうが圧倒的に速い。
また、for seq とも大差ない結果だった。
ただ、私の手元にある以下の書籍には、
BSDの一部の sh で一部使用できない演算子があったり、Solaris の sh だとそもそも使えない?らしく、移植性を重視したい場合は、expr を使ったほうが良いと記されていました(共に未検証)。

[改訂新版] シェルスクリプト基本リファレンス  ??#!/bin/shで、ここまでできる (WEB+DB PRESS plus)

[改訂新版] シェルスクリプト基本リファレンス  ??#!/bin/shで、ここまでできる (WEB+DB PRESS plus)

しかし、Linux 上で使うだけであれば、算術式展開で十分だと思われる。