俺のhcaslがこんなに遅いわけがない

id:eel3:20151102:1446476928 のhcasl、TDM-GCC 4.8.1でビルドしたC言語C++版バイナリを手元のCygwin(64bit)で実行すると激遅なの。

$ make -f Makefile_mingw
g++ -finput-charset=cp932 -Wall -std=c++11 -pedantic    hcasl.cpp   -o hcasl
$ time perl -e 'print("a" x 1000000)' | ./hcasl >/dev/null

real    0m42.853s
user    0m0.015s
sys     0m0.077s
$ _

ところが、gprofでプロファイルをとってみても、どこも遅くない。不思議!

頭を抱えたのだが、山勘でリダイレクト先を/dev/nullから普通のファイルに変更したら速くなった。

$ time perl -e 'print("a" x 1000000)' | ./hcasl >zzzz.txt

real    0m2.278s
user    0m0.015s
sys     0m0.030s
$ _

しかし、さらに不思議なことに、他の言語による実装では、リダイレクト先を/dev/nullから普通のファイルに変更しても、実行速度はそれほど変化しなかった。

Cygwinbashではなくコマンドプロンプトを使用して、リダイレクト先をNULにした場合にも、実行速度は激遅だった。/dev/null(つまりCygwin)に起因する問題ではなく、Windows自体に契機となりそうな何かがありそうだ。

Windows側だけでなくTDM-GCCにも何か問題があるのだろうか? こんな小ツールを作り、TDM-GCCでビルドしたバイナリと、Visual Studio 2013でビルドしたバイナリとで、実行時間を比較してみた。

/* test.c */
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int c;

    while ((c = fgetc(stdin)) != EOF)
        fputc(c, stdout);

    return EXIT_SUCCESS;
}
$ time ./test.exe <big-file.txt >/dev/null

TDM-GCCVisual Studio 2013、どちらも同じくらい遅かった。えっ?

Perl版hcaslで作成した8.58MBのテキストを入力として計測した値がこちら。

コンパイラ realの値
TDM-GCC 0m40.950s
Visual Studio 2013 0m41.824s

どちらも40秒強かかっている。C言語版hcaslよりわずかに速い程度だ。

ここでさらに山勘で、標準入出力をバイナリモードにしてみた。

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>

#include <fcntl.h>
#include <io.h>
#ifndef STDIN_FILENO
#	define STDIN_FILENO 0
#endif
#ifndef STDOUT_FILENO
#	define STDOUT_FILENO 1
#endif

int main(void)
{
    int c;

    errno = 0;
    if (_setmode(STDIN_FILENO, O_BINARY) == -1) {
        perror("_setmode");
        return EXIT_FAILURE;
    }
    errno = 0;
    if (_setmode(STDOUT_FILENO, O_BINARY) == -1) {
        perror("_setmode");
        return EXIT_FAILURE;
    }

    while ((c = fgetc(stdin)) != EOF)
        fputc(c, stdout);

    return EXIT_SUCCESS;
}

TDM-GCCVisual Studio 2013、どちらも劇的に速くなった!

先ほどと同じ条件で計測しなおした値がこちら。

コンパイラ realの値
TDM-GCC 0m2.543s
Visual Studio 2013 0m2.465s

40秒強から2.5秒へと、大幅に高速になっている。

テキストモードとバイナリモードの違いといえば、改行コードの変換をするか否かぐらいしか思い浮かばないが……C言語版とC++版のhcaslが遅いのは、Windowsのテキストモードの入出力で実行される改行コードの変換に起因する、ということだろうか?

しかし、hcaslでは、テキストモードでもリダイレクト先がファイルなら高速だった。つまり、テキストモードでコマンドプロンプトのNULないしCygwinの/dev/nullにリダイレクトした場合だけ遅い? なんで?

回避策は分かったが、謎が残ってしまった。