sed(1) VS printf(1) VS awk(1) リターンズ

id:eel3:20141011:1412954857 の続き。いや、CygwinではなくLinuxで計測してみただけですがな。

Cygwinは少々特殊な環境だし、そもそもWindows上ではアンチウイルスソフトその他の影響でプロセス生成コストが高くなる。Unix環境でのプロセス生成コストとは大違いだ。

という訳で、前回とは別のマシン(Celeron 847にメモリ4GBの自作PC)のUbuntu 12.04にて計測してみた。

$ uname -a
Linux fabrico 3.2.0-68-generic-pae #102-Ubuntu SMP Tue Aug 12 22:23:54 UTC 2014 i686 i686 i386 GNU/Linux
$ seq --version
seq (GNU coreutils) 8.13
Copyright (C) 2011 Free Software Foundation, Inc.
ライセンス 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.

作者 Ulrich Drepper。
$ sed --version
GNU sed バージョン 4.2.1
Copyright (C) 2009 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE,
to the extent permitted by law.

訳注: 非常に重要な文章のため、原文を残しています。
  -- 参考訳
これはフリー・ソフトウェアです。複製の条件に関しては、ソースをご覧くださ
い。保証は一切ありません。営利目的や法で定められた範囲での特定目的のため
の適合性もありません。

GNU sed ホームページ: <http://www.gnu.org/software/sed/>.
GNU ソフトウェアを使用する際の一般的なヘルプ: <http://www.gnu.org/gethelp/>.
電子メールによるバグ報告の宛先: <bug-gnu-utils@gnu.org>
報告の際、“Subject:” フィールドのどこかに “sed” を入れてください。
翻訳に関するバグは<translation-team-ja@lists.sourceforge.net>に報告してください。
$ printf --version
bash: printf: --: 無効なオプションです
printf: 使用法: printf [-v var] format [arguments]
$ bash --version
GNU bash, バージョン 4.2.25(1)-release (i686-pc-linux-gnu)
Copyright (C) 2011 Free Software Foundation, Inc.
ライセンス GPLv3+: GNU GPL バージョン 3 またはそれ以降 <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.
$ time seq 1 10000000 | sed 's/^.*$/echo & !/' >/dev/null

real    0m20.650s
user    0m34.554s
sys     0m0.184s
$ time seq 1 10000000 | xargs printf 'echo %s !\n' >/dev/null

real    0m18.112s
user    0m27.122s
sys     0m4.308s
$ time seq 1 10000000 | xargs printf 'echo %d !\n' >/dev/null

real    0m18.667s
user    0m29.234s
sys     0m4.420s

Cygwin上よりも遥かに高速ですね! ハードウェア・スペック的には今回の環境の方が圧倒的に劣るのだが。あと、Cygwinでの計測結果とは異なり、printf(1)を使用したほうが2秒ぐらい速い。

awk(1)ではどうだろうか?

$ awk --version
GNU Awk 3.1.8
Copyright (C) 1989, 1991-2010 Free Software Foundation.

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see http://www.gnu.org/licenses/.
$ time seq 1 10000000 | awk '{ print "echo " $0 " !" }' >/dev/null

real    0m14.850s
user    0m25.714s
sys     0m0.168s
$ time seq 1 10000000 | awk '{ printf("echo %s !\n", $0) }' >/dev/null

real    0m14.765s
user    0m26.606s
sys     0m0.156s
$ time seq 1 10000000 | awk '{ printf("echo %d !\n", $0) }' >/dev/null

real    0m34.866s
user    0m49.091s
sys     0m0.324s

意外にも、データを文字列として扱っている限りは、実時間もCPU時間もsed(1)より少ない。Cygwinの時と同様に、printfで%dを使用した場合は遅くなるようだ。文字列から数値へ、数値から文字列へと二重に変換が発生するためだろう。

Ubuntu 12.04では、gawk(1)をインストールしていない場合のawk(1)はmawk(1)だ。どう違うだろうか?

$ mawk -W version
mawk 1.3.3 Nov 1996, Copyright (C) Michael D. Brennan

compiled limits:
max NF             32767
sprintf buffer      1020
$ time seq 1 10000000 | mawk '{ print "echo " $0 " !" }' >/dev/null

real    0m14.631s
user    0m19.089s
sys     0m0.284s
$ time seq 1 10000000 | mawk '{ printf("echo %s !\n", $0) }' >/dev/null

real    0m14.734s
user    0m21.501s
sys     0m0.228s
$ time seq 1 10000000 | mawk '{ printf("echo %d !\n", $0) }' >/dev/null

real    0m14.770s
user    0m24.254s
sys     0m0.256s

gawk(1)とmawk(1)のどちらも、今回の処理内容においては、データを文字列として扱っている限り実時間は同程度で、CPU時間はわずかにmawk(1)の方が小さかった。しかし興味深いことに、printfでデータを文字列ではなく数値として扱った際に、mawk(1)では処理時間が全く遅くならなかった。

おまけとして、今回もOpen usp Tukubaiのmojihame(1)で計測をば。

$ python --version
Python 2.7.3
$ mojihame
Usage   : mojihame <template> <data>            (通常)
        : mojihame -l <label> <template> <data> (行単位)
        : mojihame -h <label> <template> <data> (階層データ)
Option  :  -d[c]
Version : Fri Oct 21 11:26:06 JST 2011
          Open usp Tukubai (LINUX+FREEBSD/PYTHON2.4/UTF-8)
$ echo 'echo %1 !' >template.txt
$ time seq 1 10000000 | mojihame -l template.txt >/dev/null

real    6m59.530s
user    7m11.759s
sys     0m0.328s

Cygwinよりも遅くなってしまった! 何となく、露骨にハードウェア性能の差が出た結果であるようにも思える。usp Tukubaiのmojihame(1)での実力値を見てみたいなあ。

では最後に、whileループで締めくくろう。このUbuntuにはbash以外にdashとbusyboxがインストールされているので、それぞれの比較もしてみた。

$ /bin/bash
$ time seq 1 10000000 | while read i; do echo echo $i '!'; done >/dev/null

real    6m8.858s
user    5m12.944s
sys     1m10.476s
$ exit
$ /bin/dash 
$ time seq 1 10000000 | while read i; do echo echo $i '!'; done >/dev/null 
14.79user 0.28system 1:50.95elapsed 13%CPU (0avgtext+0avgdata 2592maxresident)k
0inputs+0outputs (0major+207minor)pagefaults 0swaps
$ exit
$ /bin/busybox sh


BusyBox v1.18.5 (Ubuntu 1:1.18.5-1ubuntu4.1) built-in shell (ash)
Enter 'help' for a list of built-in commands.

~ $ time seq 1 10000000 | while read i; do echo echo $i '!'; done >/dev/null
real    2m 35.94s
user    0m 14.16s
sys     0m 0.25s
~ $ exit

Cygwinではmojihame(1)よりもbashのwhileループの方が遅かったが、Ubuntuでは逆転している。とはいえ、やはり遅いことには変わりがない。この結果も、露骨にハードウェア性能の差が出たためではないかと感じる。

dashやbusyboxのshのwhileループは、bashよりも高速だった。なるほど、よく「bashは遅い」という記述を目にするが、純粋に内部コマンド(ビルトインコマンド)のみで勝負した場合、bashは他のUnixシェルよりも遅いのだろう*1

しかし、いくらdashやbusyboxのshのwhileループが高速だといえども、sed(1)などのテキストフィルタを使う方法にはるかに及ばないようだ。

*1:もっともシェルスクリプトを書く場合、dashなどの高速なUnixシェルでは外部コマンドが必要となる場面にて、bashでは独自拡張された内部コマンドで済む場合がある。そのような箇所が多ければ多いほど、実はbashで書いた方が高速になるという逆転現象が起こる可能性が出てくる。