stdoutのフラッシュを避けたいのはなぜですか?

コードレビューで質問に遭遇しました。ある回答では、フィードバックは次のとおりでした。 std::endlはストリームをフラッシュするため、避けてください。完全な引用は次のとおりです。

一般的にstd::endlを避けることをお勧めします。ストリームに行を追加すると、ストリームがフラッシュされます。改行が必要ですが、ストリームをフラッシュすることはほとんどないため、通常は\ nと書く方が適切です。まれに、実際にフラッシュが必要な場合は、明示的に行います:std::cout << "\n" << std::flush;

ポスターは投稿でもコメントでも、これを説明しないでください。だから私の質問はこれだけです:

なぜフラッシュを避けたいのですか?

さらに興味をそそられたのは、ポスターに「フラッシュしたいというのは非常にまれだ」と書かれていることです。フラッシュを避けたい状況を想像するのは問題ありませんが、それでも一般的にはそうしたいと思っていました。改行を印刷するときにフラッシュします。結局のところ、std::endlが最初にフラッシュする理由ではありませんか?

事前に賛成票にコメントするだけです:

私はこの意見に基づくとは考えていません。あなたが好むべきものは意見に基づいているかもしれませんが、考慮に入れるべき客観的な理由があります。これまでの答えはこれを証明しています。フラッシュはパフォーマンスに影響します。

コメント

回答

短くて簡単な回答std::endlを使用すると、出力が大幅に遅くなる可能性があります。実際、私はstd::endlがC ++ iostreamがCスタイルのI / Oよりも大幅に遅いという概念のほとんどの原因であると合理的に確信しています。

たとえば、次のようなプログラムについて考えてみます。

#include <iostream> #include <string> #include <sstream> #include <time.h> #include <iomanip> #include <algorithm> #include <iterator> #include <stdio.h> char fmt[] = "%s\n"; static const int count = 3000000; static char const *const string = "This is a string."; static std::string s = std::string(string) + "\n"; void show_time(void (*f)(), char const *caption) { clock_t start = clock(); f(); clock_t ticks = clock()-start; std::cerr << std::setw(30) << caption << ": " << (double)ticks/CLOCKS_PER_SEC << "\n"; } void use_printf() { for (int i=0; i<count; i++) printf(fmt, string); } void use_puts() { for (int i=0; i<count; i++) puts(string); } void use_cout() { for (int i=0; i<count; i++) std::cout << string << "\n"; } void use_cout_unsync() { std::cout.sync_with_stdio(false); for (int i=0; i<count; i++) std::cout << string << "\n"; std::cout.sync_with_stdio(true); } void use_stringstream() { std::stringstream temp; for (int i=0; i<count; i++) temp << string << "\n"; std::cout << temp.str(); } void use_endl() { for (int i=0; i<count; i++) std::cout << string << std::endl; } void use_fill_n() { std::fill_n(std::ostream_iterator<char const *>(std::cout, "\n"), count, string); } void use_write() { for (int i = 0; i < count; i++) std::cout.write(s.data(), s.size()); } int main() { show_time(use_printf, "Time using printf"); show_time(use_puts, "Time using puts"); show_time(use_cout, "Time using cout (synced)"); show_time(use_cout_unsync, "Time using cout (un-synced)"); show_time(use_stringstream, "Time using stringstream"); show_time(use_endl, "Time using endl"); show_time(use_fill_n, "Time using fill_n"); show_time(use_write, "Time using write"); return 0; } 

標準出力がファイルにリダイレクトされると、次の結果が生成されます。

 Time using printf: 0.208539 Time using puts: 0.103065 Time using cout (synced): 0.241377 Time using cout (un-synced): 0.181853 Time using stringstream: 0.223617 Time using endl: 4.32881 Time using fill_n: 0.209951 Time using write: 0.102781 

std::endlを使用すると、この場合、プログラムの速度が約20倍遅くなります。短い文字列を記述した場合、スローダウンはさらに大きくなる可能性があります。

ストリームを手動でフラッシュしたい場合がいくつかありますが、正直なところ、かなりの数であり、その間にあります。

ほとんどの場合、ストリームをフラッシュする必要があります(たとえば、プロンプトを出力してから入力を待つ)。std::tieのようなものを使用したことがない限り、ストリームは自動的に実行されます。 /またはstd::sync_with_stdioそれを防ぐために。

それはあなたが持っている本当に珍しい状況のほんの一部を残しますストリームを手動でフラッシュする正当な理由。このようなケースは非常にまれであるため、発生したときにstd::flushを使用して、コードを読んでいる人に意図的に(そしてより頻繁に)ストリームをフラッシュしていることを明らかにする価値があります。そうではないにしても、おそらく、ストリームをフラッシュすることが本当に理にかなっている場合に、これがまれなケースの1つである理由についてのコメントにも値します。

回答

プロセスが出力を生成するたびに、実際に作業を行う関数を呼び出す必要があります。ほとんどの場合、その関数は最終的にwrite(2)になります。マルチタスクオペレーティングシステムでは、write()の呼び出しはカーネルにトラップされ、プロセスを停止し、I / Oを処理し、ブロックがクリアされている間に他のことを実行する必要があります。レディキューに入れて、時が来たら再び実行します。まとめると、そのすべてのアクティビティをシステムコールオーバーヘッドで呼び出すことができます。

少量のデータを書き込んだ後、またはバッファがまったくない場合にバッファリングされたストリーム*をフラッシュすると、実行するたびにオーバーヘッドが発生します。

 1\n (System call that writes two bytes) 2\n (System call that writes two bytes) 3\n (System call that writes two bytes) 4\n (System call that writes two bytes) 5\n (System call that writes two bytes)  

これは、誰かがそれが燃えていることに気付くまで、非常に初期の頃に行われた方法です。多くのシステム時間。オーバーヘッドは、出力がいっぱいになるか、プログラムがすぐに送信する必要があると判断するまで、出力をバッファーに蓄積することで抑えることができます。(表示または消費する必要のある出力を散発的に生成する場合は、後者を実行することをお勧めします。)各行の終わりでフラッシュを回避すると、システムコールの数が減り、発生するオーバーヘッドが削減されます。

 1\n 2\n 3\n 4\n 5\n (Flush) (System call that writes ten bytes)  

*標準出力の概念は、プロセスに関連付けられたファイル記述子であることに注意してくださいこれは、C、C ++などで定義されているstdoutとは異なります。これらは、完全にユーザーランドに存在し、に書き込むバッファ付きストリームの実装の識別子です。標準出力。write()システムコールはバッファリングされません。

コメント

  • フラッシュするかどうかといつフラッシュするかその出力が使用されるコンテキストによって異なります。スループット指向のプログラムは、バッファがいっぱいになったときにのみフラッシュする必要があります。遅延の影響を受けやすいプログラムは、より頻繁にフラッシュする必要があります。たとえば、出力がコンソールに送信される場合は、sti各改行の後にフラッシュします。インタラクティブプログラム(入力プロンプトが表示される)は、行がまだいっぱいになっていない場合でも、すぐにフラッシュする必要があります。
  • "出力がコンソールに送られる場合は、 " Trueですが、出力がコンソールに送られる場合、各改行の後に自動的にフラッシュされます。明示的に行う必要はありません。
  • @amonタイムリーにフラッシュするため、つまり、入力を要求する前に、遅延を遅らせることなく、連続した出力を合体させるだけで十分です。確かに、出力の表示に遅延が多すぎたり、最適化に多大な労力を費やしたりするよりも、頻繁にフラッシュする方が適切です…
  • バッファラーが時々使用するトリックの1つは、新しいものが来るとタイマーを設定し、タイマーがなくなるとフラッシュします。

回答

フラッシュを回避する理由:

IOは、オペレーティングシステムが比較的大量のデータを処理できる場合に最適に機能するためです。少量のデータで定期的にフラッシュすると、速度が大幅に低下することがあります。

手動でフラッシュする必要がほとんどない理由:

ほとんどのユースケースをカバーする自動フラッシュがあります。たとえば、プログラムがコンソールに書き込む場合、システムはデフォルトで改行ごとにフラッシュします。または、ファイルに書き込む場合、一度に書き込むのに十分なデータがある場合、およびファイルを閉じるときにもデータが書き込まれます。

いつ手動でフラッシュする必要があります:

出力をすぐに明示的に更新する必要がある場合。例:現在の行を繰り返し上書きするスピナーまたはプログレスバーを作成する場合。または、ファイルに出力して、特定の瞬間にファイルを更新したい場合。

コメント

  • システムはユーザーバッファをフラッシュできません。または、ライブラリ、特に標準ライブラリも"システム"に含めますか?もちろん、stdinstdoutは、少なくとも両方が同じコンソールを使用している場合は、通常は結合されます。
  • はい、 'ユーザースペースライブラリとカーネルスペースの間で物事がどのように分割されているかについて、あまり詳しく説明するのは良い考えではないと思いました。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です