ベンチマークのための時間計測関数

awk を使っていると、良く時間取得関数がないものかと思われることも多いと思います。 実際、古い awk の書籍では awk 単独で時刻を取得するのではなく、date コマンド (MS-DOS のコマンド) を用いたものもありました。

awk でも時刻を取得することは可能です。 awk で epoch time を YYYY/MM/DD HH:MM:SS に変換するに nawk などでも動作する時刻取得関数を用意していますので参照してください。 今回はベンチマークのための時間計測関数で行っているようなベンチマークを行うための手法について検討してみます。

まず、全 awk 共通で使えるものとして、一般的に偶数番目の srand() がエポックタイムからの経過秒数を返すということを利用するものがあります。

#! /usr/bin/gawk -f
# bench_1.awk

BEGIN {

    srand();
    start_time = srand();

    tak(ARGV[1] ? ARGV[1] : 12, ARGV[2] ? ARGV[2] : 6, ARGV[3] ? ARGV[3] : 0);

    srand();
    end_time = srand();

    print "Elapsed Time: " end_time - start_time " (sec)";
}

# tak():    たらい回し関数 (竹内関数) を返す
#   in:     x, y, z (以下の URL を参照のこと)
#           http://ja.wikipedia.org/wiki/竹内関数
#   out:    入力された x, y, z のたらい回し関数を返す
function tak(x, y, z) {
    if (x <= y) {
        return y;
    } else {
        return tak(tak(x - 1, y, z), tak(y - 1, z, x), tak(z - 1, x, y));
    }
}

ここでは、程よく時間のかかる問題としてメモ化を行わない竹内関数 (たらい回し関数) を用いています。 上記のスクリプトで経過秒数が整数で出力されます。

$ time nawk -f bench_1.awk
Elapsed Time: 8 (sec)
nawk -f bench_1.awk  8.67s user 0.01s system 98% cpu 8.804 total

time コマンドによる出力が 8.804 秒ですが、上記手法では 8 秒となっています。 srand() 関数の戻り値を利用するのは nawk などでも使用できるのですが、戻り値が必ずエポックタイムからの経過秒数になるかどうかはコンパイルされた環境に依存します。

次に gawk では時間関数が拡張され、srand() 関数を用いなくても systime() で取得することができます。

#! /usr/bin/gawk -f
# bench_2.awk

BEGIN {

    start_time = systime();

    tak(ARGV[1] ? ARGV[1] : 12, ARGV[2] ? ARGV[2] : 6, ARGV[3] ? ARGV[3] : 0);

    end_time = systime();

    print "Elapsed Time: " end_time - start_time " (sec)";
}

# tak():    たらい回し関数 (竹内関数) を返す
#   in:     x, y, z (以下の URL を参照のこと)
#           http://ja.wikipedia.org/wiki/竹内関数
#   out:    入力された x, y, z のたらい回し関数を返す
function tak(x, y, z) {
    if (x <= y) {
        return y;
    } else {
        return tak(tak(x - 1, y, z), tak(y - 1, z, x), tak(z - 1, x, y));
    }
}

実行してみましょう。

$ time gawk -f bench_2.awk
Elapsed Time: 6 (sec)
gawk -f bench_2.awk  5.57s user 0.01s system 98% cpu 5.686 total

gawk の時間関数の拡張は awk の大きなアプリケーションへの可能性を広げることになりましたが、細かな数値を取得することはできませんでした。

この壁を越えたのが xgawk による拡張です。

gettimeofday() という関数が用意され、(ビルド環境に依存しますが) time プラグマと共に使われます。

#! /usr/bin/gawk -f
# bench_3.awk

@load time

BEGIN {

    start_time = gettimeofday();

    tak(ARGV[1] ? ARGV[1] : 12, ARGV[2] ? ARGV[2] : 6, ARGV[3] ? ARGV[3] : 0);

    end_time = gettimeofday();

    print "Elapsed Time: " end_time - start_time " (sec)";
}

# tak():    たらい回し関数 (竹内関数) を返す
#   in:     x, y, z (以下の URL を参照のこと)
#           http://ja.wikipedia.org/wiki/竹内関数
#   out:    入力された x, y, z のたらい回し関数を返す
function tak(x, y, z) {
    if (x <= y) {
        return y;
    } else {
        return tak(tak(x - 1, y, z), tak(y - 1, z, x), tak(z - 1, x, y));
    }
}

実際に実行してみます。

$ time xgawk -f bench_3.awk
Elapsed Time: 5.49308 (sec)
xgawk -f bench_3.awk  5.46s user 0.01s system 99% cpu 5.495 total

精度の問題は置いておくとしても time コマンドよりも細かな数値が出力されています。 仮にこの値が正しいなら、xgawk のロードなどのコア部以外のオーバーヘッドは 0.002 秒程度であることも分かります。 (事前に何度か実行して試しているため、キャッシュに格納されているためオーバーヘッドが極度に小さくなっていると考えられます)

さて、以上 3 つの方法で awk でのベンチマークの取り方を見てきましたが、いかがでしょうか。 nawk, gawk, xgawk と awk の進化を見ることができたのではないでしょうか。

tag_xgawk.png