web-dev-qa-db-ja.com

IOStreamのパフォーマンスを向上させる方法は?

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

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

この質問を見てください:

ファイルの行ごとの読み取りを高速化するには

最良の答えはfscanfを使用することであり、C++ ifstreamは一貫して2〜3倍遅いと思われます。

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

考慮すべき点

  • バッファリング(rdbuf()->pubsetbuf(buffer, size)
  • 同期(std::ios_base::sync_with_stdio
  • ロケール処理(切り詰められたロケールを使用できますか、それともすべて削除しますか?)

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

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

61
Matthieu M.

ここに私がこれまでに集めたものがあります:

バッファリング

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

  • 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を呼び出すのが最善です。それ以外の場合、さまざまな標準ライブラリの実装には異なる動作があります。

ロケール処理:

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

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

同期:

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

std::ios_base静的関数を使用して、global設定(sync_with_stdioの静的メンバー)にアクセスできます。

測定:

これを使って、SUSE 10p3でgcc 3.4.2を使用して-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%高速化

g ++ i686-Apple-darwin10-g ++-4.2.1(GCC)4.2.1(Apple Inc. build 5664)上の@ Bogatyr、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;
}
47
Matthieu M.

さらに2つの改善:

重い入出力の前にstd::cin.tie(nullptr);を発行してください。

引用 http://en.cppreference.com/w/cpp/io/cin

Std :: cinが構築されると、std :: cin.tie()は&std :: coutを返し、同様にstd :: wcin.tie()は&std :: wcoutを返します。つまり、std :: cinに対するフォーマットされた入力操作は、出力のために保留中の文字がある場合、std :: cout.flush()を強制的に呼び出します。

_std::cin_から_std::cout_を解放すると、バッファーのフラッシュを回避できます。これは、_std::cin_と_std::cout_の複数の混合呼び出しに関連します。 std::cin.tie(std::nullptr);を呼び出すと、出力が遅延する可能性があるため、プログラムをユーザーが対話的に実行するのに適さないことに注意してください。

関連するベンチマーク:

ファイル_test1.cpp_:

_#include <iostream>
using namespace std;

int main()
{
  ios_base::sync_with_stdio(false);

  int i;
  while(cin >> i)
    cout << i << '\n';
}
_

ファイル_test2.cpp_:

_#include <iostream>
using namespace std;

int main()
{
  ios_base::sync_with_stdio(false);
  cin.tie(nullptr);

  int i;
  while(cin >> i)
    cout << i << '\n';

  cout.flush();
}
_

両方とも_g++ -O2 -std=c++11_によってコンパイルされます。コンパイラーのバージョン:g++ (Ubuntu 4.8.4-2ubuntu1~14.04) 4.8.4(はい、かなり古いです)。

ベンチマーク結果:

_work@mg-K54C ~ $ time ./test1 < test.in > test1.in

real    0m3.140s
user    0m0.581s
sys 0m2.560s
work@mg-K54C ~ $ time ./test2 < test.in > test2.in

real    0m0.234s
user    0m0.234s
sys 0m0.000s
_

(_test.in_は1179648行で構成され、各行は単一の_5_のみで構成されています。2.4MBです。ここに投稿しないでください。).

オンライン裁判官がcin.tie(nullptr)なしでプログラムを拒否し続けたが、代わりにcin.tie(nullptr)またはprintf/scanfを使用して受け入れていたアルゴリズムタスクを解決したことを覚えています。 cin/cout

_'\n'_の代わりに_std::endl_を使用します。

引用 http://en.cppreference.com/w/cpp/io/manip/endl

出力シーケンスosに改行文字を挿入し、os.put(os.widen( '\ n'))に続いてos.flush()を呼び出して、あたかもそれをフラッシュします。

endlの代わりに_'\n'_を出力することにより、buferのフラッシュを回避できます。

関連するベンチマーク:

ファイル_test1.cpp_:

_#include <iostream>
using namespace std;

int main()
{
  ios_base::sync_with_stdio(false);

  for(int i = 0; i < 1179648; ++i)
    cout << i << endl;
}
_

ファイル_test2.cpp_:

_#include <iostream>
using namespace std;

int main()
{
  ios_base::sync_with_stdio(false);

  for(int i = 0; i < 1179648; ++i)
    cout << i << '\n';
}
_

両方とも上記のようにコンパイルされました。

ベンチマーク結果:

_work@mg-K54C ~ $ time ./test1 > test1.in

real    0m2.946s
user    0m0.404s
sys 0m2.543s
work@mg-K54C ~ $ time ./test2 > test2.in

real    0m0.156s
user    0m0.135s
sys 0m0.020s
_
15
gaazkam

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

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

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

最近ここで別のトピックを見たので、これは複製に近いものです。

1
CashCow