日刊形式でHaskellなどについての記事をだらだらと掲載しとります。
2005 | 01 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2006 | 01 | 02 | 03 | 04 | 06 | 07 | 08 | 09 | 11 |
2007 | 03 | 04 | 05 | 07 | 08 | 09 | 12 |
2008 | 02 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2009 | 03 | 05 | 06 | 09 | 10 | 11 | 12 |
2010 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 12 |
2011 | 01 | 02 | 05 |
2008年12月23日(火) C++のstreamの転送速度を調べる
■[C++]C++のstreamの転送速度を調べる

はじめに
C++のstreamはとても良くできていて、これを用いたライブラリを作りたいのだけど、
本当に(主にパフォーマンス的な理由で)大丈夫なのとかそういう話。
初めにお断りしておきますが、以下の内容はすべてlinux+gcc4.3での話です。
streamは遅い
ふつうにistreamからget()して、ostreamにputしてるとめちゃくちゃ遅い。
C言語のgetchar, putcharより10進数で1.5桁ぐらい遅いよ。
istream::readとかででかいブロック読めば大丈夫なのだけど、
細かい単位で読みたいことの方が多いよね。
そういうわけで、そういう場合にも速く転送することが可能なのかどうか調べてみる。
テストプログラム
istreamの内容をostreamに転送するプログラムを6通り書いた。
その1:普通のプログラム
void copy1(ostream &os, istream &is) { for (char c; is.get(c); os<<c); }
その2:ブロックごとに読み書き
今回の趣旨には会わないが速度の比較のため。
(readに失敗したときの処理があってるのか不明)
void copy2(ostream &os, istream &is) { for (;;){ char buf[1024]; if (!is.read(buf, 1024)) break; os.write(buf, 1024); } copy1(os, is); }
その3:istream_iteratorを使う
is>>c; os<<c;と同じような意味だろうから、
空白がとばされてるような気がするので、
入力には空白は入れないように配慮。
void copy3(ostream &os, istream &is) { istream_iterator<char> p(is), end; ostream_iterator<char> q(os); copy(p, end, q); }
その4:istreambuf_iteratorを使う
空白は大丈夫です。
void copy4(ostream &os, istream &is) { istreambuf_iterator<char> p(is), end; ostreambuf_iterator<char> q(os); copy(p, end, q); }
その5:ostreamにstreambufを突っ込む
あんまり柔軟性は無いけど、
この書き方がどのぐらいの速度なのか調べたかった。
void copy5(ostream &os, istream &is)
{
os<<is.rdbuf();
}
その6:istreamからstreambufに突っ込む
5と同じぐらいの速度になるという予測。
void copy6(ostream &os, istream &is)
{
is>>os.rdbuf();
}
六つ書いたけど、期待しているのはその4だけです。
これがどういうケースで速くなるのかを調べたい。
テスト
これらのルーチンを、ifstream&ofstreamと、cin&cout、
さらにそれぞれ組み合わせと、出力が/dev/nullになってるときの
ファイルサイズは1GB、
file -> file(普通のファイル)
| copy1 | 40.310 |
| copy2 | 3.340 |
| copy3 | 46.900 |
| copy4 | 1.550 |
| copy5 | 1.870 |
| copy6 | 1.620 |
file -> file(/dev/null)
| copy1 | 38.610 |
| copy2 | 0.880 |
| copy3 | 45.390 |
| copy4 | 0.400 |
| copy5 | 0.390 |
| copy6 | 0.410 |
file -> stdout(普通のファイルにリダイレクト)
| copy1 | 47.780 |
| copy2 | 3.560 |
| copy3 | 53.990 |
| copy4 | 3.330 |
| copy5 | 4.120 |
| copy6 | 4.060 |
file -> stdout(/dev/nullにリダイレクト)
| copy1 | 56.530 |
| copy2 | 0.700 |
| copy3 | 66.580 |
| copy4 | 0.510 |
| copy5 | 0.500 |
| copy6 | 0.510 |
stdin -> file
| copy1 | 69.860 |
| copy2 | 3.390 |
| copy3 | 98.580 |
| copy4 | 34.540 |
| copy5 | 35.980 |
| copy6 | 34.880 |
stdin -> /dev/null
| copy1 | 68.000 |
| copy2 | 0.890 |
| copy3 | 100.190 |
| copy4 | 34.180 |
| copy5 | 34.040 |
| copy6 | 34.410 |
stdin -> stdout(file)
| copy1 | とても長い(>300) |
| copy2 | 6.080 |
| copy3 | とても長い |
| copy4 | 46.160 |
| copy5 | 70.310 |
| copy6 | 70.530 |
stdin -> stdout(/dev/null)
| copy1 | とても長い |
| copy2 | 0.870 |
| copy3 | とても長い |
| copy4 | 44.830 |
| copy5 | 44.210 |
| copy6 | 44.350 |
とても長いと書いてあるところは、長すぎたので切りました。
5分で切りましたが、多分10分たってもおわらないのじゃないかな。
考察
出力が/dev/nullか普通のファイルかは当然/dev/nullの方が速いものの、
systemの時間が変わるだけです。
計測する必要なかったな。
これも当然ながら。
copy1はすべてにおいて遅い。
copy4,5,6は同じぐらいの速度になった。
streambuf_iteratorだけ遅いという結果にならなくてよかった。
標準入力->標準出力で4と5,6で結果がだいぶ違う原因は不明。
以下はstreambuf_iteratorについて。
入力がifstreamの時はおしなべて速い。
入力がcinの時は速くない。
でも、cin.get()とかで読み取るよりは速い。
出力はofstreamでcoutでも大差はなし。
どちらかというとofstreamを使う方が速いようだ。
cin, coutが実際どういう型になっているのかは知らないが、
まあ実装がfstreamと違うのだろう。
速度の違いの理由を調べるため、straceをかけてみた。
基本的にどれもある程度まとまってread/writeされていた。
(サイズは8KB,4KB,1KB様々だが)
つまり、速度の違いはそれ以外のオーバーヘッドである。
しかし、許容しがたいほど遅かったもの(cin->coutのcopy1,3)は
出力が一文字ずつwriteされていた。
これは如何に。
たしかにバッファリングされている。
同じcoutへの出力なのになぜ?
調べるために、次の2つのコードを書いた。
for (char c; cin.get(c); ) cout<<c;
ifstream ifs("tmp"); for (char c; ifs.get(c); ) cout<<c;
すると、やはり、なんと、前者はバッファリングされず、後者はされるのだ。
私はこの結果を見たとき、
と思わずにはいられなかった。
今回この原因を探るのはめんどいのでしないが、
まあそのうち調べたいと思う。というか、調べないといかんのだろうなあ。
read/writeとの比較
void fdcopy(int to, int from) { char buf[1024*8]; for (int n; (n=read(from, buf, sizeof(buf)))>0; ){ char *p=buf, *q=p+n; while(p<q){ ssize_t ret=write(to, p, q-p); assert(ret>0); p+=ret; } } }
結果
| file-> file | 1.280 |
| file -> /dev/null | 0.390 |
| stdin -> stdout | 2.430 |
| stdin -> stdout(/dev/null) | 0.380 |
streambuf_iteratorのオーバーヘッドは1割程度と言うことになる。
(バッファのサイズが違ったりするので、単純には言えないけども)
まとめ
まとめて読むことが可能なら、istream::read(), ostream::write()を使おう。
cinからのistreambuf_iterator経由での入力はあまり速くない。
でも、get()などを呼ぶことに比べたら速い。
ifstreamからのistreambuf_iteratorはとても速い。
ostreambuf_iteratorは大体とても速い。
結論としては、streambuf_iteratorは十分速い、十分使える。
つまり、stream使っといて大丈夫。
コンパイルオプションを変えると
遅い方法でもコンパイラがよろしく最適化してくれて
十分な速度で実行されたりしますか?
std::cin.tie(0);
std::ios::sync_with_stdio(false);
を入れてベンチマークとったらどうなるでしょうか.
ですね。
>The tied stream is another output stream object which is
>flushed before each i/o operation in this stream object.
がいかにも怪しい。