web-dev-qa-db-ja.com

std :: fstreamバッファリングと手動バッファリング(手動バッファリングで10倍のゲインが必要な理由)

2つの書き込み構成をテストしました。

1)Fstreamのバッファリング:

// Initialization
const unsigned int length = 8192;
char buffer[length];
std::ofstream stream;
stream.rdbuf()->pubsetbuf(buffer, length);
stream.open("test.dat", std::ios::binary | std::ios::trunc)

// To write I use :
stream.write(reinterpret_cast<char*>(&x), sizeof(x));

2)手動バッファリング:

// Initialization
const unsigned int length = 8192;
char buffer[length];
std::ofstream stream("test.dat", std::ios::binary | std::ios::trunc);

// Then I put manually the data in the buffer

// To write I use :
stream.write(buffer, length);

私は同じ結果を期待していました...

しかし、私の手動バッファリングは、100MBのファイルを書き込むためにパフォーマンスを10倍改善し、fstreamバッファリングは通常の状況と比較して何も変更しません(バッファを再定義しません)。

誰かがこの状況の説明を持っていますか?

編集:ここにニュースがあります:スーパーコンピューターで行われたベンチマーク(Linux 64ビットアーキテクチャ、Intel Xeon 8コア、Lustreファイルシステム、...うまくいけば適切に構成されたコンパイラー) benchmark (1kBの手動バッファーの「共鳴」の理由については説明しません...)

編集2:そして1024 Bでの共鳴(誰かがそれについて考えているなら、私は興味があります): enter image description here

54
Vincent

これは基本的に、関数呼び出しのオーバーヘッドと間接化によるものです。 ofstream :: write()メソッドはostreamから継承されます。その関数は、オーバーヘッドの最初の原因であるlibstdc ++ではインライン化されません。次に、ostream :: write()はrdbuf()-> sputn()を呼び出して実際の書き込みを行う必要があります。これは仮想関数呼び出しです。

さらに、libstdc ++は、sputn()を別の仮想関数呼び出しを追加する別の仮想関数xsputn()にリダイレクトします。

自分でバッファに文字を入れると、そのオーバーヘッドを回避できます。

25
Vaughn Cato

2番目のチャート のピークの原因は何かを説明したいと思います

実際、_std::ofstream_で使用される仮想関数は、最初の写真で見たようにパフォーマンスを低下させますが、手動バッファーサイズが1024バイト未満のときに最高のパフォーマンスが得られた理由については答えがありません。

この問題は、writev()およびwrite())システムコールの高コストと、_std::filebuf_の内部クラス_std::ofstream_の内部実装に関連しています。

write()がパフォーマンスにどのように影響するかを示すために、Linuxマシンでddツールを使用して、さまざまなバッファーサイズ(bsオプション)で10MBファイルをコピーする簡単なテストを行いました。

_test@test$ time dd if=/dev/zero of=zero bs=256 count=40000
40000+0 records in
40000+0 records out
10240000 bytes (10 MB) copied, 2.36589 s, 4.3 MB/s

real    0m2.370s
user    0m0.000s
sys     0m0.952s
test$test: time dd if=/dev/zero of=zero bs=512 count=20000
20000+0 records in
20000+0 records out
10240000 bytes (10 MB) copied, 1.31708 s, 7.8 MB/s

real    0m1.324s
user    0m0.000s
sys     0m0.476s

test@test: time dd if=/dev/zero of=zero bs=1024 count=10000
10000+0 records in
10000+0 records out
10240000 bytes (10 MB) copied, 0.792634 s, 12.9 MB/s

real    0m0.798s
user    0m0.008s
sys     0m0.236s

test@test: time dd if=/dev/zero of=zero bs=4096 count=2500
2500+0 records in
2500+0 records out
10240000 bytes (10 MB) copied, 0.274074 s, 37.4 MB/s

real    0m0.293s
user    0m0.000s
sys     0m0.064s
_

バッファが少ないほど、書き込み速度が遅くなり、ddがシステム空間で費やす時間が長くなることがわかります。そのため、バッファサイズが小さくなると、読み取り/書き込み速度が低下します。

しかし、なぜトピッククリエーターの手動バッファーテストで手動バッファーサイズが1024バイト未満だったのが最高速度だったのでしょうか。なぜほぼ一定だったのですか?

説明は_std::ofstream_実装、特に_std::basic_filebuf_に関連しています。

デフォルトでは、1024バイトのバッファー(BUFSIZ変数)を使用します。したがって、1024未満のピースを使用してデータを書き込むと、writev()ではなくwrite()システムコールが2回のofstream::write()操作に対して少なくとも1回呼び出されます(ピースのサイズは1023です) <1024-最初はバッファーに書き込まれ、2番目は1番目と2番目の書き込みを強制します)。これに基づいて、ofstream::write()速度はピーク前の手動バッファサイズに依存しないと結論付けることができます(write()はまれに少なくとも2回呼び出されます)。

ofstream::write()呼び出しを使用して1024バイト以上のバッファーを一度に書き込むと、_ofstream::write_ごとにwritev()システム呼び出しが呼び出されます。そのため、手動バッファーが1024を超えると(ピーク後)速度が向上することがわかります。

さらに、streambuf::pubsetbuf()を使用して1024バッファー(たとえば、8192バイトのバッファー)よりも大きい_std::ofstream_バッファーを設定し、ostream::write()を呼び出して1024サイズのピースを使用してデータを書き込む場合、書き込み速度が1024バッファを使用するのと同じになることに驚くでしょう。 _std::basic_filebuf_の実装-_std::ofstream_の内部クラスが強制的にハードコードされている呼び出しシステムwritev()渡されたバッファが1024バイト以上の場合の各ofstream::write()呼び出し( basic_filebuf :: xsputn() ソースコードを参照) 2014-11-05 で報告されたGCC bugzillaにも未解決の問題があります。

したがって、この問題の解決は、2つの可能なケースを使用して実行できます。

  • _std::filebuf_を独自のクラスに置き換え、_std::ofstream_を再定義します
  • ofstream::write()に渡す必要があるバッファを1024未満のピースに分割し、それらを1つずつofstream::write()に渡します
  • _std::ofstream_の仮想関数のパフォーマンスの低下を避けるために、小さなデータをofstream::write()に渡さないでください。
5
nomad85

既存の応答に、大きなブロックのデータを書き込む場合、このパフォーマンス動作(仮想メソッド呼び出し/間接化によるすべてのオーバーヘッド)は通常問題にならないことを付け加えます。質問とこれらの以前の回答から省略されているように思われます(おそらく暗黙的に理解されていますが)は、元のコードが毎回少数のバイトを書き込んでいたということです。他の人のために明確にするために:データの大きなブロック(〜kB +)を書いている場合、手動バッファリングがstd::fstreamのバッファリング。

0
wolf1oo