背景
ゲーム開発フレームワークを作る上で、ロガーが欲しくなった。 C言語のみで記述している都合上、言語(あるいは主要言語処理系)標準のロガーは存在しない。 文字列を出力するだけの簡単なもので良いため、外部ライブラリを用いるのは大げさである。 従って、簡単なロガーを実装することにした。
ロガーを実装するには、高速な標準出力の手法を把握していなければならない。 lwlogを見たところ、fwriteとsetvbufだけで高速になるように見える。
これが本当か否かぱっと調べたところ、printfとfwrite・あるいはバッファリング有無の速度比較に関する文献がない。 あまりにも検証が簡単だからであろうか。 それとも、あまりにも初級的な議論であるからであろうか。
ということで、簡単な実験をしたため、結果をまとめる。
プログラム
既定の文字列と外部から与えられた文字列を、10000行(一行ずつflush)出力するプログラムにおいて、次の四パターンを検証した。
- printf
- fwrite
- printf(バッファリング有り)
- fwrite(バッファリング有り)
環境は以下。
- Windows 10 Home
- gcc.exe (x86_64-win32-seh-rev2, Built by MinGW-W64 project) 12.2.0
- Powershell
ソースコードは以下。
gccの-Dオプションでパターンを切り替えられるようにした。
gcc -DPRINTF -DIOFBF main.c
のようにビルドして欲しい。
#include <stdio.h>
#include <string.h>
#include <windows.h>
LARGE_INTEGER freq;
LARGE_INTEGER before;
LARGE_INTEGER after;
void test_printf(const char *msg) {
QueryPerformanceCounter(&before);
for (int i = 0; i < 10000; ++i) {
printf("hello %s\n", msg);
#ifdef IOFBF
fflush(stdout);
#endif
}
QueryPerformanceCounter(&after);
}
void test_fwrite(const char *msg) {
QueryPerformanceCounter(&before);
for (int i = 0; i < 10000; ++i) {
fwrite("hello ", sizeof(char), 6, stdout);
fwrite(msg, sizeof(char), strlen(msg), stdout);
fwrite("\n", sizeof(char), 1, stdout);
#ifdef IOFBF
fflush(stdout);
#endif
}
QueryPerformanceCounter(&after);
}
int main() {
QueryPerformanceFrequency(&freq);
#ifdef IOFBF
setvbuf(stdout, NULL, _IOFBF, 1024);
#endif
#ifdef PRINTF
test_printf("world!");
printf("printf\n");
#elif FWRITE
test_fwrite("world!");
printf("fwrite\n");
#endif
#ifdef IOFBF
printf("with _IOFBF\n");
#endif
printf("%lf ms\n", (after.QuadPart - before.QuadPart) * 1000.0 / freq.QuadPart);
return 0;
}
ラインバッファリング _IOLBF
を用いるべきであろうが、何故か期待通りにflushされなかったため、フルバッファリング _IOFBF
にした上で一行ずつ fflush()
を呼び出すことにした。
結果
五回実行した。 結果を以下にまとめる。 単位はミリ秒(ms)である。 小数点以下は切り捨てる。
printf | fwrite | printf(_IOFBF) | fwrite(_IOFBF) | |
---|---|---|---|---|
1 | 4992 | 2439 | 425 | 432 |
2 | 6060 | 1919 | 542 | 430 |
3 | 4722 | 2411 | 440 | 542 |
4 | 6099 | 1876 | 544 | 461 |
5 | 5080 | 1923 | 433 | 543 |
Average | 5390 | 2113 | 476 | 481 |
バッファリングなしでは圧倒的にfwriteが速く、バッファリングありでは両者大差ないという結果を得られた。
printfは整形をする分遅いであろうと想定できるが、であればバッファリングありで大差ない理由がわからない。 この点は検証の余地がある。
取り敢えずロガーは、取り回しの良いバッファリングありのprintfで実装するのが良さそうだ。
■