C/C++ におけるデータ入力の速度

100 万行のテキストファイル(test-data)を C/C++ で作成したプログラムで読み込むとき,どのくらいの時間がかかるかを調べた結果です.

データ入力がボトルネックになるような状況では,std::fgets(), std::fread(), std::istream::read() を使った方が良さそうです.std::istream については特に極端な差が出ていますので,速度面を重視する場合,便利なインタフェースを封印しないとダメっぽいです.実に惜しい….

追記(2010-07-28):id:metaboles さんより,std::ios::sync_with_stdio(false) を使えば std::cin.getline() や std::getline() も std::fgets() と同じくらい速くなるというコメントをいただきました(後述).

$ wc test-data
 1000000   990919 97251910 test-data
入力方法 time -f 'real %E, user %U, sys %S' 速度 [MB/s]
std::fgetc() real 0:01.38, user 1.34, sys 0.04 70.47
std::getchar() real 0:01.40, user 1.31, sys 0.07 69.47
std::istream::get() real 0:06.22, user 6.08, sys 0.04 15.64
std::istream::operator>>(char &) real 0:10.22, user 9.90, sys 0.03 9.52
std::istream::operator>>(std::string &) real 0:05.97, user 5.82, sys 0.04 16.29
std::fgets() real 0:00.18, user 0.16, sys 0.02 540.29
std::istream::getline() real 0:05.56, user 5.40, sys 0.08 17.49
std::getline() real 0:05.82, user 5.70, sys 0.05 16.71
std::fread() real 0:00.04, user 0.00, sys 0.05 2,431.30
std::istream::read() real 0:00.04, user 0.00, sys 0.05 2,431.30

※ テキストファイルがキャッシュされている状態で実行しています.

実験に使ったソースコードはこちら(http://sites.google.com/site/headdythehero/cabine/2010/0725/measure-input-speed.tar.gz?attredirects=0&d=1)にあります.かなり適当です(もちろん悪い意味で…).

増補版(2010-07-28)

std::setvbuf() や std::ios_base::sync_with_stdio() を使うと速度が上がるというコメントをいただいたので,条件をいろいろと追加して再実験しました.おまけとして,入力データを 1000 万行に増やしています.

結果を見る限り,std::setvbuf() の影響は小さいようです.といっても,標準入力からデータを受け取るだけのプログラムなので,バッファのサイズを大きくしてデータを先読みする旨味がなかったことが理由だと思います.入力が複数あったり,入力の合間に何らかの処理をおこなうようなプログラムであれば,効果がありそうです.

一方,std::ios_base::sync_with_stdio() については,劇的に速度が改善されました.std::getline() や std::cin.getline() の速度は 300 倍くらいになっています.std::fgets() と比べて少し遅いくらいなので,十分に実用的な速度が出ています.

感謝:n-yo さん,id:metaboles さん

wc test-data
 10000000   9910931 975315208 test-data
関数 補足 ※ real user sys 速度 [MB/s]
std::fgetc - 0:13.85 13.31 0.40 70.42
std::getchar - 0:13.75 13.40 0.34 70.93
std::cin.get - 0:59.30 58.93 0.32 16.45
std::cin.get nosync 0:24.79 23.97 0.42 39.34
::operator>> char 1:39.93 98.36 0.42 9.76
::operator>> char, nosync 0:31.95 31.18 0.40 30.53
::operator>> std::string 0:58.95 57.74 0.32 16.54
::opreator>> std::string, nosync 0:04.34 3.82 0.38 224.73
std::fgets - 0:01.71 1.32 0.36 570.36
std::fgets _IOLBF 0:01.71 1.30 0.37 570.36
std::fgets _IOFBF 0:01.70 1.36 0.29 573.71
std::cin.getline - 0:55.01 54.17 0.29 1.77
std::cin.getline nosync 0:01.88 1.56 0.32 518.48
std::getline - 0:57.23 56.82 0.25 1.70
std::getline nosync 0:02.07 1.67 0.40 471.17
std::fread - 0:00.35 0.00 0.36 2786.61
std::fread _IONBF 0:00.35 0.01 0.34 2786.61
std::fread _IOFBF 0:00.47 0.14 0.33 2075.14
std::cin.read - 0:00.37 0.04 0.33 2635.99
::LineReader.read 自作 0:01.99 1.57 0.42 490.11

※ 1. nosync: std::ios_base::sync_with_stdio(false)
※ 2. _IONBF: std::setvbuf(stdin, NULL, _IONBF, 0)
※ 3. _IOLBF: std::setvbuf(stdin, io_buf, _IOLBF, 1048576)
※ 4. _IOFBF: std::setvbuf(stdin, io_buf, _IOFBF, 1048576)
※ 5. real, user, sys: time -f 'real %E, user %U, sys %S'

# sync_with_stdio() については,std::ios::sync_with_stdio() という呼び出しの他,ストリーム単位で std::cin.sync_with_stdio() という呼び出しもできるようです.