64

C を学んだほとんどの C++ ユーザーは、C++ でコーディングしている場合でも、関数のprintf/scanfファミリを使用することを好みます。

インターフェイスの方がはるかに優れていることは認めますが (特に POSIX に似た形式とローカリゼーション)、圧倒的な懸念事項はパフォーマンスであるようです。

この質問を見てみましょう:

ファイルの行ごとの読み取りを高速化するにはどうすればよいですか

最良の答えは使用することfscanfであり、C++ifstreamは一貫して 2 ~ 3 倍遅いようです。

IOStreams のパフォーマンスを改善するための「ヒント」のリポジトリをコンパイルできれば、何が機能し、何が機能しないかを考えることができれば素晴らしいと思いました。

考慮すべき点

  • バッファリング ( rdbuf()->pubsetbuf(buffer, size))
  • 同期 ( std::ios_base::sync_with_stdio)
  • ロケール処理 (縮小されたロケールを使用するか、それとも完全に削除できますか?)

もちろん、他のアプローチも大歓迎です。

注: Dietmar Kuhl による「新しい」実装が言及されましたが、それに関する多くの詳細を見つけることができませんでした。以前の参照はリンク切れのようです。

4

3 に答える 3

50

これまでに集めたものは次のとおりです。

バッファリング:

デフォルトでバッファが非常に小さい場合、バッファ サイズを大きくするとパフォーマンスが確実に向上します。

  • HDDのヒット数を減らします
  • システムコールの数を減らします

streambufバッファーは、基になる実装にアクセスすることで設定できます。

char Buffer[N];

std::ifstream file("file.txt");

file.rdbuf()->pubsetbuf(Buffer, N);
// the pointer reader by rdbuf is guaranteed
// to be non-null after successful constructor

@iavr の厚意による警告: cppreferenceによると、ファイルを開く前に呼び出すことをお勧めしpubsetbufます。それ以外の場合、さまざまな標準ライブラリの実装は異なる動作をします。

ロケール処理:

Locale は、数字や日付が関係する文字変換、フィルタリング、およびより巧妙なトリックを実行できます。それらは動的ディスパッチと仮想呼び出しの複雑なシステムを通過するため、それらを削除するとペナルティ ヒットを削減するのに役立ちます。

デフォルトのCロケールは、マシン間で統一されているだけでなく、変換を実行しないことを意図しています。使用するのに適したデフォルトです。

同期:

この機能を使用しても、パフォーマンスの向上は見られませんでした。

static 関数を使用して、グローバル設定 ( の静的メンバー) にアクセスできます。std::ios_basesync_with_stdio

測定:

gcc 3.4.2これで遊んで、SUSE 10p3 withを使用してコンパイルされた単純なプログラムをいじりました-O2

C: 7.76532e+06
C++: 1.0874e+07

これは、デフォルト コードの約20%... の速度低下を表します。実際、バッファー (C または C++) または同期パラメーター (C++) を改ざんしても、改善は見られませんでした。

他の人による結果:

@Irfy on g++ 4.7.2-2ubuntu1、-O3、仮想化された Ubuntu 11.10、3.5.0-25-generic、x86_64、十分な RAM/CPU、196MB の複数の「find / >> largefile.txt」実行

C: 634572 C++: 473222

C++ 25% 高速化

@Matteo Italia on g++ 4.4.5、-O3、Ubuntu Linux 10.10 x86_64、ランダムな 180 MB ファイル

C: 910390
C++: 776016

C++ 17% 高速化

@Bogatyr on g++ i686-apple-darwin10-g++-4.2.1 (GCC) 4.2.1 (Apple Inc. build 5664)、mac mini、4GB RAM、168MB データファイルを使用したこのテストを除いてアイドル状態

C: 4.34151e+06
C++: 9.14476e+06

C++ 111% 遅い

@Asu on clang++ 3.8.0-2ubuntu4、Kubuntu 16.04 Linux 4.8-rc3、8GB RAM、i5 Haswell、Crucial SSD、88MB データファイル (tar.xz アーカイブ)

C: 270895 C++: 162799

C++ 66% 高速化

答えは次のとおりです。これは実装の品質の問題であり、実際にはプラットフォームに依存します:/

ベンチマークに関心のある方向けの完全なコードは次のとおりです。

#include <fstream>
#include <iostream>
#include <iomanip>

#include <cmath>
#include <cstdio>

#include <sys/time.h>

template <typename Func>
double benchmark(Func f, size_t iterations)
{
  f();

  timeval a, b;
  gettimeofday(&a, 0);
  for (; iterations --> 0;)
  {
    f();
  }
  gettimeofday(&b, 0);
  return (b.tv_sec * (unsigned int)1e6 + b.tv_usec) -
         (a.tv_sec * (unsigned int)1e6 + a.tv_usec);
}


struct CRead
{
  CRead(char const* filename): _filename(filename) {}

  void operator()() {
    FILE* file = fopen(_filename, "r");

    int count = 0;
    while ( fscanf(file,"%s", _buffer) == 1 ) { ++count; }

    fclose(file);
  }

  char const* _filename;
  char _buffer[1024];
};

struct CppRead
{
  CppRead(char const* filename): _filename(filename), _buffer() {}

  enum { BufferSize = 16184 };

  void operator()() {
    std::ifstream file(_filename, std::ifstream::in);

    // comment to remove extended buffer
    file.rdbuf()->pubsetbuf(_buffer, BufferSize);

    int count = 0;
    std::string s;
    while ( file >> s ) { ++count; }
  }

  char const* _filename;
  char _buffer[BufferSize];
};


int main(int argc, char* argv[])
{
  size_t iterations = 1;
  if (argc > 1) { iterations = atoi(argv[1]); }

  char const* oldLocale = setlocale(LC_ALL,"C");
  if (strcmp(oldLocale, "C") != 0) {
    std::cout << "Replaced old locale '" << oldLocale << "' by 'C'\n";
  }

  char const* filename = "largefile.txt";

  CRead cread(filename);
  CppRead cppread(filename);

  // comment to use the default setting
  bool oldSyncSetting = std::ios_base::sync_with_stdio(false);

  double ctime = benchmark(cread, iterations);
  double cpptime = benchmark(cppread, iterations);

  // comment if oldSyncSetting's declaration is commented
  std::ios_base::sync_with_stdio(oldSyncSetting);

  std::cout << "C  : " << ctime << "\n"
               "C++: " << cpptime << "\n";

  return 0;
}
于 2011-03-02T13:52:01.677 に答える
1

興味深いことに、C プログラマーは C++ を書くときに printf を好むと言っています。出力を使用coutして書き込む以外に C であるコードがたくさんあるからです。iostream

多くの場合、直接使用することでパフォーマンスが向上しfilebufます (Scott Meyers は効果的な STL でこれについて述べています) が、filebuf 直接の使用に関するドキュメントは比較的少なく、ほとんどの開発者std::getlineは、ほとんどの場合、どちらが簡単かを好みます。

ロケールに関しては、ファセットを作成すると、多くの場合、すべてのファセットで一度ロケールを作成し、それを保存して、使用する各ストリームに組み込むことでパフォーマンスが向上します。

最近、ここで別のトピックを見たので、これはほぼ重複しています。

于 2011-03-02T11:29:52.683 に答える