Cを学んだほとんどのC++ユーザーは、C++でコーディングしている場合でも、printf
/scanf
ファミリーの関数を使用することを好みます。
インターフェイスの方が優れていると認めていますが(特にPOSIXのような形式とローカリゼーション)、パフォーマンスが圧倒的な懸念事項のようです。
この質問を見てください:
最良の答えはfscanf
を使用することであり、C++ ifstream
は一貫して2〜3倍遅いと思われます。
IOStreamsのパフォーマンス、機能するもの、機能しないものを改善するために「ヒント」のリポジトリをコンパイルできたら素晴らしいと思いました。
考慮すべき点
rdbuf()->pubsetbuf(buffer, size)
)std::ios_base::sync_with_stdio
)もちろん、他のアプローチも歓迎します。
注:Dietmar Kuhlによる「新しい」実装が言及されましたが、それに関する多くの詳細を見つけることができませんでした。以前の参照はリンク切れのようです。
ここに私がこれまでに集めたものがあります:
バッファリング:
デフォルトでバッファが非常に小さい場合、バッファサイズを大きくするとパフォーマンスが確実に向上します。
バッファは、基になる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;
}
さらに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
_
興味深いことに、Cプログラマーは、C++を記述するときにprintfを好むと言います。出力を書き込むためにcout
とiostream
を使用する以外のCのコードがたくさんあるからです。
多くの場合、filebuf
を直接使用することでパフォーマンスが向上します(Scott MeyersがEffective STLでこれについて言及しました)。filebufを直接使用することに関するドキュメントは比較的少なく、ほとんどの開発者はstd::getline
これはほとんどの場合単純です。
ロケールに関しては、ファセットを作成する場合、すべてのファセットで一度ロケールを作成し、保存したままにして、使用する各ストリームに埋め込むことで、パフォーマンスが向上することがよくあります。
最近ここで別のトピックを見たので、これは複製に近いものです。