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() という呼び出しもできるようです.