Thursday, October 2, 2014

sprintf を最大10倍以上高速化するプリプロセッサ「qrintf」を作った

最近H2OというHTTPサーバを書いているのですが、プロファイルを取ってみるとsprintfが結構な時間を食っていて不満に感じていました。実際、sprintfは数値や文字列をフォーマットするのに十徳ナイフ的に便利なので、HTTPサーバに限らず良く使われる(そしてCPU時間を消費しがちな)関数です。

では、sprintfを最適化すれば、様々なプログラムが より高速に動作するようになるのではないでしょうか。ということで作ったのが、qrintfです。

qrintfは、Cプリプロセッサのラッパーとしてソースコードに含まれるsprintfの呼出フォーマットを解析し、フォーマットにあわせたコードに書き換えることで、sprintfを高速化します。

たとえば、以下のようなIPv4アドレスを文字列化するコード片を
sprintf(
    buf,
    "%d.%d.%d.%d",
    (addr >> 24) & 0xff,
    (addr >> 16) & 0xff,
    (addr >> 8) & 0xff,
    addr & 0xff);
以下のようにコンパイル時に自動的に書き換え、実行時にフォーマット文字列の解析する負荷を省くことで大幅な高速化を実現しています。
((int)(_qrintf_d(
    _qrintf_c(
        _qrintf_d(
            _qrintf_c(
                _qrintf_d(
                    _qrintf_c(
                        _qrintf_d(
                            _qrintf_init(buf),
                            (addr >> 24) & 0xff),
                        '.'),
                    (addr >> 16) & 0xff),
                '.'),
            (addr >> 8) & 0xff),
        '.'),
    addr & 0xff)
).off);

たとえば上記のコードの場合、以下のように、qrintfを用いることで10倍以上文字列化処理が高速化することを確認しています。
$ gcc -O2 examples/ipv4addr.c
$ time ./a.out 1234567890
result: 73.150.2.210

real    0m2.602s
user    0m2.598s
sys 0m0.003s
$ ./qrintf-gcc -O2 examples/ipv4addr.c
$ time ./a.out 1234567890
result: 73.150.2.210

real    0m0.196s
user    0m0.192s
sys 0m0.003s

sprintfを呼んでいるコードを書き換えずに高速化できて、ハッピーですね!

2 comments: