速度 - なぜC++では標準読み込みの行がPythonよりずっと遅いのですか?




zig 言語 (7)

私はPythonとC ++を使ってstdinから文字列を読み込む行を比較したいと思っていました。私のC ++コードが相当するPythonコードよりも遅く実行されているのがわかりました。 私のC ++は錆びていて、私はまだPythonistaの専門家ではないので、何か間違っているのか、何かを誤解しているのかどうか教えてください。

(TLDR答え:ステートメントを含めます: cin.sync_with_stdio(false)か、代わりにfgets使用してください。

TLDRの結果:私の質問の一番下までスクロールして、テーブルを見てください。)

C ++コード:

#include <iostream>
#include <time.h>

using namespace std;

int main() {
    string input_line;
    long line_count = 0;
    time_t start = time(NULL);
    int sec;
    int lps;

    while (cin) {
        getline(cin, input_line);
        if (!cin.eof())
            line_count++;
    };

    sec = (int) time(NULL) - start;
    cerr << "Read " << line_count << " lines in " << sec << " seconds.";
    if (sec > 0) {
        lps = line_count / sec;
        cerr << " LPS: " << lps << endl;
    } else
        cerr << endl;
    return 0;
}

// Compiled with:
// g++ -O3 -o readline_test_cpp foo.cpp

同等のPython:

#!/usr/bin/env python
import time
import sys

count = 0
start = time.time()

for line in  sys.stdin:
    count += 1

delta_sec = int(time.time() - start_time)
if delta_sec >= 0:
    lines_per_sec = int(round(count/delta_sec))
    print("Read {0} lines in {1} seconds. LPS: {2}".format(count, delta_sec,
       lines_per_sec))

ここに私の結果があります:

$ cat test_lines | ./readline_test_cpp
Read 5570000 lines in 9 seconds. LPS: 618889

$cat test_lines | ./readline_test.py
Read 5570000 lines in 1 seconds. LPS: 5570000

私はMac OS X v10.6.8(Snow Leopard)とLinux 2.6.32(Red Hat Linux 6.2)の両方でこれを試したことに注意してください。 前者はMacBook Proであり、後者は非常に軽いサーバーであり、これはあまり適切ではありません。

$ for i in {1..5}; do echo "Test run $i at `date`"; echo -n "CPP:"; cat test_lines | ./readline_test_cpp ; echo -n "Python:"; cat test_lines | ./readline_test.py ; done
Test run 1 at Mon Feb 20 21:29:28 EST 2012
CPP:   Read 5570001 lines in 9 seconds. LPS: 618889
Python:Read 5570000 lines in 1 seconds. LPS: 5570000
Test run 2 at Mon Feb 20 21:29:39 EST 2012
CPP:   Read 5570001 lines in 9 seconds. LPS: 618889
Python:Read 5570000 lines in 1 seconds. LPS: 5570000
Test run 3 at Mon Feb 20 21:29:50 EST 2012
CPP:   Read 5570001 lines in 9 seconds. LPS: 618889
Python:Read 5570000 lines in 1 seconds. LPS: 5570000
Test run 4 at Mon Feb 20 21:30:01 EST 2012
CPP:   Read 5570001 lines in 9 seconds. LPS: 618889
Python:Read 5570000 lines in 1 seconds. LPS: 5570000
Test run 5 at Mon Feb 20 21:30:11 EST 2012
CPP:   Read 5570001 lines in 10 seconds. LPS: 557000
Python:Read 5570000 lines in  1 seconds. LPS: 5570000

小さなベンチマーク補遺と要約

完全性のために、私は元の(同期された)C ++コードで同じボックスの同じファイルの読み込み速度を更新すると思った。 ここでも、これは高速ディスク上の100M行ファイル用です。 ここでは、いくつかのソリューション/アプローチを用いて比較します。

Implementation      Lines per second
python (default)           3,571,428
cin (default/naive)          819,672
cin (no sync)             12,500,000
fgets                     14,285,714
wc (not fair comparison)  54,644,808

scanf( "%s")は文字列を解析し、任意のスペース(char、space、tab、newline)を探すため、これがまだ遅い理由は2番目の例(scanf

また、はい、CPythonは、ハードディスクの読み取りを避けるためにいくつかのキャッシュを行います。


getline 、ストリーム演算子、 scanf 、ファイルの読み込み時間や小さなテキストファイルを読み込んでいる場合に便利です。 しかし、パフォーマンスが気になるものなら、ファイル全体をメモリにバッファリングするだけで済みます(それが合うと仮定して)。

ここに例があります:

//open file in binary mode
std::fstream file( filename, std::ios::in|::std::ios::binary );
if( !file ) return NULL;

//read the size...
file.seekg(0, std::ios::end);
size_t length = (size_t)file.tellg();
file.seekg(0, std::ios::beg);

//read into memory buffer, then close it.
char *filebuf = new char[length+1];
file.read(filebuf, length);
filebuf[length] = '\0'; //make it null-terminated
file.close();

必要に応じて、このようなより便利なアクセスのために、バッファを囲むようにストリームをラップすることができます:

std::istrstream header(&filebuf[0], length);

また、ファイルを管理している場合は、テキストの代わりにフラットなバイナリデータ形式を使用することを検討してください。 空白のあいまいさをすべて処理する必要がないため、読み書きがより確実です。 また、解析するのがより小さく、はるかに高速です。


ちなみに、C ++バージョンの行数がPythonバージョンの数よりも大きい理由は、eofを超えて読み込もうとするとeofフラグがセットされるということだけです。 正しいループは次のようになります:

while (cin) {
    getline(cin, input_line);

    if (!cin.eof())
        line_count++;
};

デフォルトでは、 cinはstdioと同期されます。これにより、入力バッファリングが回避されます。 これをメインの上部に追加すると、はるかに優れたパフォーマンスが得られるはずです。

std::ios_base::sync_with_stdio(false);

通常、入力ストリームがバッファリングされると、一度に1つの文字を読み込むのではなく、ストリームがより大きな塊で読み込まれます。 これにより、通常は比較的高価なシステムコールの数が減少します。 しかし、 FILE*ベースのstdioiostreamsしばしば別々の実装を持ち、バッファを分離しているので、両方を一緒に使用すると問題になる可能性があります。 例えば:

int myvalue1;
cin >> myvalue1;
int myvalue2;
scanf("%d",&myvalue2);

実際に必要とするより多くの入力がcinによって読み取られた場合、2番目の整数値は独自の独立したバッファを持つscanf関数では使用できません。 これは予期せぬ結果につながります。

これを避けるために、デフォルトでストリームはstdio同期されstdio 。 これを達成する一般的な方法の1つは、 stdio関数を使用して必要に応じてcinが一度に1つずつ文字を読み込ませることです。 残念ながら、これは多くのオーバーヘッドを引き起こします。 少量の入力については、これは大きな問題ではありませんが、何百万行も読んでいるときには、パフォーマンス上のペナルティが大きくなります。

幸運なことに、図書館の設計者は、あなたが何をしているのかを知っていれば、この機能を無効にしてパフォーマンスを向上させることができると判断し、 sync_with_stdioメソッドを提供しました。


次のコードは、今までここに投稿されている他のコードよりも速いものでした(Visual Studio 2013,64ビット、行長が一様に500MBのファイル、[0、1000])。

const int buffer_size = 500 * 1024;  // Too large/small buffer is not good.
std::vector<char> buffer(buffer_size);
int size;
while ((size = fread(buffer.data(), sizeof(char), buffer_size, stdin)) > 0) {
    line_count += count_if(buffer.begin(), buffer.begin() + size, [](char ch) { return ch == '\n'; });
}

それは、私のPythonのすべての試みを2倍以上打ちました。


私はMac上でg ++を使って私のコンピュータ上で元の結果を再現しました。

whileループの直前にC ++バージョンに次のステートメントを追加すると、 Pythonバージョンでインライン展開されます。

std::ios_base::sync_with_stdio(false);
char buffer[1048576];
std::cin.rdbuf()->pubsetbuf(buffer, sizeof(buffer));

sync_with_stdioは速度を2秒に改善し、大きなバッファを設定すると1秒に短縮しました。


答えの最初の要素: <iostream>は遅いです。 くそー。 私は以下のようにscanf大きなパフォーマンス向上を見せていますが、それでもPythonより2倍遅いです。

#include <iostream>
#include <time.h>
#include <cstdio>

using namespace std;

int main() {
    char buffer[10000];
    long line_count = 0;
    time_t start = time(NULL);
    int sec;
    int lps;

    int read = 1;
    while(read > 0) {
        read = scanf("%s", buffer);
        line_count++;
    };
    sec = (int) time(NULL) - start;
    line_count--;
    cerr << "Saw " << line_count << " lines in " << sec << " seconds." ;
    if (sec > 0) {
        lps = line_count / sec;
        cerr << "  Crunch speed: " << lps << endl;
    } 
    else
        cerr << endl;
    return 0;
}




getline