1. ホーム
  2. python

[解決済み] なぜC++はPythonよりもstdinからの行の読み込みが遅いのですか?

2022-03-18 21:50:15

質問

PythonとC++を使用して標準入力から文字列の行を読むことを比較したかったのですが、私のC++コードが同等のPythonコードよりも一桁遅く実行されるのを見てショックを受けました。私のC++は錆びつき、私はまだPythonの専門家ではないので、私が何か間違ったことをしているか、私が何かを誤解しているかどうか教えてください。


( 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行のファイルに対するものです。以下は、いくつかの解決策/アプローチとの比較です。

<テーブル 実装 行数/秒 python (デフォルト) 3,571,428 MIN (デフォルト/ネイブ) 819,672 シンク(同期なし) 12,500,000 フィッツ 14,285,714 wc (公正な比較ではない) 54,644,808

解決方法

tl;dr: C++のデフォルト設定が異なるため、より多くのシステムコールを必要とします。

デフォルトでは cin は stdio と同期しており、入力バッファリングを回避しています。 これをmainの先頭に追加すると、パフォーマンスが大幅に改善されるはずです。

std::ios_base::sync_with_stdio(false);

通常、入力ストリームがバッファリングされている場合、一度に1文字ずつ読むのではなく、より大きなチャンクで読み込まれます。 これにより、一般的に比較的高価なシステムコールの数を減らすことができます。 しかし FILE* ベース stdioiostreams は実装が別々であることが多く、したがってバッファも別々であるため、両者を併用すると問題が発生する可能性があります。 例えば

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

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

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

幸いなことに、ライブラリの設計者は、自分が何をしているかを知っていれば、この機能を無効にしてパフォーマンスを向上させることもできるはずだと判断し、そのような機能を提供するために sync_with_stdio というメソッドがあります。このリンクから(強調)。

同期をオフにすると、C++標準ストリームは独立してI/Oをバッファリングすることができるようになります。 場合によってはかなり速くなる .