web-dev-qa-db-ja.com

より高速な出力のためにcoutとprintfを混合する

いくつかのテストを実行した後、printfcoutよりもはるかに高速であることに気付きました。私はそれが実装に依存していることを知っていますが、私のLinuxボックスではprintfは8倍高速です。したがって、私の考えは、2つの印刷方法を混合することです。単純な印刷にはcoutを使用し、巨大な出力(通常はループ内)の生成にはprintfを使用する予定です。他のメソッドに切り替える前にフラッシュすることを忘れない限り、安全だと思います:

cout << "Hello" << endl;
cout.flush();

for (int i=0; i<1000000; ++i) {
    printf("World!\n");
}
fflush(stdout);

cout << "last line" << endl;
cout << flush;

いいですか?

更新:すべての貴重なフィードバックに感謝します。答えの要約:トリッキーなソリューションを避けたい場合は、endlcoutと一緒に使用しないでください。これは、バッファーを暗黙的にフラッシュするためです。使用する "\n"代わりに。 largeの出力を生成すると興味深い場合があります。

47
Jabba

直接的な答えは、はい、それは大丈夫です。

多くの人が速度を向上させる方法についてさまざまなアイデアを出してきましたが、どちらが最も効果的であるかについてはかなりの意見の相違があるようです。私は簡単なテストプログラムを作成して、少なくともどの手法が何を実行したかを理解することにしました。

_#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;
}
_

VC++ 2013(x86バージョンとx64バージョンの両方)でコンパイルした後、これをWindowsで実行しました。 1回の実行からの出力(出力がディスクファイルにリダイレクトされる)は次のようになります。

_          Time using printf: 0.953
            Time using puts: 0.567
   Time using cout (synced): 0.736
Time using cout (un-synced): 0.714
    Time using stringstream: 0.725
            Time using endl: 20.097
          Time using fill_n: 0.749
           Time using write: 0.499
_

予想どおり、結果はさまざまですが、興味深い点がいくつかあります。

  1. nULデバイスへの書き込み時、printf/putsはcoutよりはるかに高速です
    • しかし、実際のファイルに書き込むとき、coutはかなりうまくいきます
  2. かなりの数の提案された最適化ではほとんど効果がありません
    • 私のテストでは、fill_nは他の何よりも高速です
  3. 断然最大の最適化はendlを回避することです
  4. cout.writeが最速の時間を提供しました(ただし、大幅なマージンではない可能性があります)

私は最近、printfを強制的に呼び出すようにコードを編集しました。 Anders Kaseorgは、_g++_が特定のシーケンスprintf("%s\n", foo);を認識し、puts(foo);と同等であることを指摘して、それに応じてコードを生成します(つまり、putsを呼び出すコードを生成します) printfの代わりに)。フォーマット文字列をグローバル配列に移動し、それをフォーマット文字列として渡すと、同じ出力が生成されますが、printfではなくputsを介して強制的に生成されます。もちろん、いつかはこのあたりで最適化される可能性もありますが、少なくとも今のところ(g ++ 5.1)_g++ -O3 -S_を使用したテストは、実際にprintfを呼び出していることを確認します(前のコードはputsの呼び出しにコンパイルされています)。 。

69
Jerry Coffin

_std::endl_をストリームに送信すると、newlineが追加されてストリームがフラッシュされます。 cout.flush()のその後の呼び出しは不要です。これがcoutprintfのタイミングで行われた場合は、リンゴとリンゴを比較していませんでした。

19
William Bell

デフォルトでは、CおよびC++の標準出力ストリームは同期されているため、一方に書き込むと他方がフラッシュされるため、明示的なフラッシュは必要ありません。

12
anon

また、C++ストリームはCストリームに同期されることに注意してください。
したがって、同期を保つために余分な作業を行います。

注意すべきもう1つのことは、ストリームを同じ量でフラッシュすることを確認することです。ストリームを1つのシステムで継続的にフラッシュし、他のシステムではフラッシュしないと、テストの速度に確実に影響します。

一方が他方より速いと仮定する前に、次のことを行う必要があります。

  • c I/OからのC++ I/Oの同期を解除します(sync_with_stdio()を参照)。
  • フラッシュの量が同等であることを確認してください。
12
Martin York

printfのバッファーサイズを増やすことで、stdoutのパフォーマンスをさらに向上させることができます。

setvbuf (stdout, NULL, _IOFBF, 32768);  // any value larger than 512 and also a
                  // a multiple of the system i/o buffer size is an improvement

I/Oを実行するためのオペレーティングシステムへの呼び出し回数は、ほとんどの場合、最も高価なコンポーネントとパフォーマンスリミッターです。

もちろん、coutの出力がstdoutと混ざっている場合、バッファーフラッシュは、増加したバッファーサイズの目的を無効にします。

9
wallyk

sync_with_stdio C++を作成するにはIOを高速化します。

cout.sync_with_stdio(false);

coutを使用して出力パフォーマンスを向上させる必要があります。

5
Juan

printfcoutの間のパフォーマンスを気にする必要はありません。パフォーマンスを向上させたい場合は、フォーマットされた出力をフォーマットされていない出力から分離します。

puts("Hello World\n")printf("%s", "Hellow World\n")よりもはるかに高速です。 (主にフォーマットのオーバーヘッドによる)フォーマットされたテキストをプレーンテキストから分離したら、次のようなトリックを実行できます。

const char hello[] = "Hello World\n";
cout.write(hello, sizeof(hello) - sizeof('\0'));

フォーマットされた出力を高速化するための秘訣は、文字列に対してすべてのフォーマットを実行してから、文字列(またはバッファー)でブロック出力を使用することです。

const unsigned int MAX_BUFFER_SIZE = 256;
char buffer[MAX_BUFFER_SIZE];
sprintf(buffer, "%d times is a charm.\n", 5);
unsigned int text_length = strlen(buffer) - sizeof('\0');
fwrite(buffer, 1, text_length, stdout);

プログラムのパフォーマンスをさらに改善するには、出力の量を減らします。出力が少ないほど、プログラムは高速になります。副作用として、実行可能ファイルのサイズも小さくなります。

3
Thomas Matthews

まあ、実際にcoutを使って正直になる理由は考えられません。すべてのファイルに含まれるような非常に単純なことを実行するために巨大でかさばるテンプレートを作成することは、完全に異常です。また、それは可能な限りタイピングが遅くなるように設計されているようで、何百万回も<<<<を入力してからその間に値を入力し、何か>> variableName >>>を偶然取得すると、二度とそれをやりたくありません。

言うまでもなく、std名前空間を含めると、世界は最終的に内破されます。そうしないと、入力の負担がさらに馬鹿げたものになります。

ただし、printfもあまり好きではありません。私にとっての解決策は、私自身の具象クラスを作成し、その中で必要なioスタッフを呼び出すことです。次に、好きな方法で、必要な実装やフォーマットなどを使用して、本当に単純なioを作成できます(一般的に、フロートを常に1つの方法にする必要があります。たとえば、理由もなく800の方法でフォーマットしないようにします。呼び出しごとのフォーマットでは冗談です)。

つまり、入力するのはdout +のようなものです。これは、「+ debugIoType +」の「+ cPlusPlusMethod +」よりも正気です。少なくともIMO。 dout ++;

しかし、あなたはあなたが望むものは何でも持つことができます。たくさんのファイルがあるので、これがコンパイル時間をどれほど改善するかは驚くべきことです。

また、CとC++を混在させることには何の問題もありません。慎重に行う必要があります。最初にCを使用する際に問題を引き起こすものを使用している場合は、CとCを混在させることによる心配は最小限であると言っても安全です。 C++。

1

C++とCのiomethodsを混在させることは、私のC++の本、FYIに対して推奨されていました。 C関数は、C++が予期する/保持する状態を踏みにじると確信しています。

0
Paul Nathan