2016-06-18 10 views
2

このC++コードのパフォーマンスを向上させ、それが基になっているCコードと同等にする方法を理解しようとしています。C++ iostreamとC stdioのパフォーマンス/オーバーヘッド

#include <stdlib.h> 
#include <stdio.h> 
#include <string.h> 

typedef struct point { 
    double x, y; 
} point_t; 

int read_point(FILE *fp, point_t *p) { 
    char buf[1024]; 
    if (fgets(buf, 1024, fp)) { 
    char *s = strtok(buf, " "); 
    if (s) p->x = atof(s); else return 0; 
    s = strtok(buf, " "); 
    if (s) p->y = atof(s); else return 0; 
    } 
    else 
    return 0; 
    return 1; 
} 

int main() { 
    point_t p; 
    FILE *fp = fopen("biginput.txt", "r"); 

    int i = 0; 
    while (read_point(fp, &p)) 
    i++; 

    printf("read %d points\n", i); 
    return 0; 
} 

C++のコードは次のようになります:

#include <iostream> 
#include <fstream> 

using namespace std; 

struct point { 
    double x, y; 
}; 

istream &operator>>(istream &in, point &p) { 
    return in >> p.x >> p.y; 
} 

int main() { 
    point p; 
    ifstream input("biginput.txt"); 

    int i = 0; 
    while (input >> p) 
    i++; 

    cout << "read " << i << " points" << endl; 
    return 0; 
} 

私はC++のコードが短く、より直接的であるが、私は私のマシン上でそれらの両方を実行したときにそのようなCのコードは次のようになります(両方とも138メガバイトのテストファイルに対して同じマシン上で実行されている)非常に異なるパフォーマンスを得る:

$ time ./test-c 
read 10523988 points 
    1.73 real   1.68 user   0.04 sys 
# subsequent runs: 
    1.69 real   1.64 user   0.04 sys 
    1.72 real   1.67 user   0.04 sys 
    1.69 real   1.65 user   0.04 sys 

$ time ./test-cpp 
read 10523988 points 
    14.50 real  14.36 user   0.07 sys 
# subsequent runs 
    14.79 real  14.43 user   0.12 sys 
    14.76 real  14.40 user   0.11 sys 
    14.58 real  14.36 user   0.09 sys 
    14.67 real  14.40 user   0.10 sys 

をC++バージョンがABであるという結果は変わりません連続してプログラムのいずれかを何度も実行します10倍も遅くなります。

587.96 600.12 
430.44 628.09 
848.77 468.48 
854.61 76.18 
240.64 409.32 
428.23 643.30 
839.62 568.58 

私が欠けているのオーバーヘッドを削減するトリックがあります:

ファイル形式は、以下のようなスペースで区切られたダブルスの行だけを、ありますか?

編集1:これは本当に問題を解決していません

14.62 real  14.47 user   0.07 sys 
    14.54 real  14.39 user   0.07 sys 
    14.58 real  14.43 user   0.07 sys 
    14.63 real  14.45 user   0.08 sys 
    14.54 real  14.32 user   0.09 sys 

:オペレーターのインラインを作る非常に小さいが、おそらく検出可能な効果を持っているようです。

編集2:私は打ち鳴らすを使用しています:

$ clang --version 
Apple LLVM version 7.0.0 (clang-700.0.72) 
Target: x86_64-apple-darwin15.5.0 
Thread model: posix 

私はCまたはC++のいずれか上の任意の最適化レベルを使用していないと、彼らは両方のクラン上の同じバージョンでコンパイルされています私のMac。 OS X 10.11でXcode(/ usr/bin/clang)に付属するバージョンでしょう。私は1つではなく他のコンパイラで最適化を有効にするか、別のコンパイラを使用すると問題を解決すると考えました。

編集3:何か他

istream &operator>>を置き換える私はCのバージョンに近づくようにIStreamの演算子を書き換えてきたし、それが改善されているが、私はまだ〜5倍のパフォーマンスギャップを参照してください。

inline istream &operator>>(istream &in, point &p) { 
    string line; 
    getline(in, line); 

    if (line.empty()) 
    return in; 

    size_t next = 0; 
    p.x = stod(line, &next); 
    p.y = stod(line.substr(next)); 
    return in; 
} 

実行します

$ time ./test-cpp 
read 10523988 points 
    6.85 real   6.74 user   0.05 sys 
# subsequently 
    6.70 real   6.62 user   0.05 sys 
    7.16 real   6.86 user   0.12 sys 
    6.80 real   6.59 user   0.09 sys 
    6.79 real   6.59 user   0.08 sys 

興味深いことに、-O3でこれをコンパイルすることは実質的な改善である:

$ time ./test-cpp 
read 10523988 points 
    2.44 real   2.38 user   0.04 sys 
    2.43 real   2.38 user   0.04 sys 
    2.49 real   2.41 user   0.04 sys 
    2.51 real   2.42 user   0.05 sys 
    2.47 real   2.40 user   0.05 sys 

編集4:CのものとのIStreamオペレータの本体を>>交換

この場合、

inline istream &operator>>(istream &in, point &p) { 
    char buf[1024]; 
    in.getline(buf, 1024); 
    char *s = strtok(buf, " "); 
    if (s) 
    p.x = atof(s); 
    else 
    return in; 

    s = strtok(NULL, " "); 
    if (s) 
    p.y = atof(s); 

    return in; 
} 

タイミング、それが最適化されていない最適化が最適化されていないC(Cはまだかかわらず、勝利最適化された)の上にそれを置く2秒の領土で私たちを取得します。上には、Cのパフォーマンスに非常に近くなります。最適化せずに、正確には:

2.13 real   2.08 user   0.04 sys 
    2.14 real   2.07 user   0.04 sys 
    2.33 real   2.15 user   0.05 sys 
    2.16 real   2.10 user   0.04 sys 
    2.18 real   2.12 user   0.04 sys 
    2.33 real   2.17 user   0.06 sys 

で:最適化と

1.16 real   1.10 user   0.04 sys 
    1.19 real   1.13 user   0.04 sys 
    1.11 real   1.06 user   0.03 sys 
    1.15 real   1.09 user   0.04 sys 
    1.14 real   1.09 user   0.04 sys 

C、ちょうどリンゴ対リンゴ行うには:私は私が一緒に暮らすことができたとし

0.81 real   0.77 user   0.03 sys 
    0.82 real   0.78 user   0.04 sys 
    0.87 real   0.80 user   0.04 sys 
    0.84 real   0.77 user   0.04 sys 
    0.83 real   0.78 user   0.04 sys 
    0.83 real   0.77 user   0.04 sys 

これは、初心者のC++ユーザーとして、私は今疑問に思っています:

  1. これを別の方法でやってみる価値はありますか?私はistreamオペレータの中で何が起こるかは重要ではないと思う>>。
  2. これらの3つの方法以外にも優れたC++コードを構築する別の方法はありますか?
  3. これはイディオムですか?そうでない場合、ほとんどの人はそれが何であるかのパフォーマンスを受け入れるだけですか?

編集5:この質問はprintf関数についての答えは全く異なる、私はこれはおそらくアドレスに直接この上記3点のいずれかの重複がどのようにリンクされ、質問が表示されません。

+4

例はlikeと似ていません。 C++バージョンはストリームオブジェクトからすべての値を読み込み、 'fscanf("%f&f "、&p-> x、&p-> y)'のようなものを使用することに匹敵します。 Cバージョンは、 'fscanf()'呼び出しのパフォーマンスを打ち破るために手作業で作られています。 C++のバージョンは、 'std :: getline()'や 'std :: istream :: getline()'を使い、文字列を解析するために同様に作られました。 – Peter

+0

@Peter私は誰もfscanfを実際に使用しているとは思わない。それは非常に脆いです。しかし、私は 'getline'を使用するよう提案された変更を行うことを嬉しく思っています。それ以降は、文字列ストリームを作成し、iostreamの代わりに' >> 'を使用します。 –

+0

@Peter私はここで完璧なベンチマークを作成しようとしているわけではありません。実際には、C++の初心者ユーザとして実際にはC++コードを実際に加速することに興味があります。 –

答えて

2

パフォーマンスに大きな違いが生じる原因は、全体的な機能に大きな違いがあります。

私は一見、同等のアプローチを詳細に比較するために最善を尽くします。 Cにおいて

は:

が改行または検出されるか、または最大長(1024)-のファイル終わりに到達するまで

  • 読み取り文字をループ
  • トークン化は、ハードコード白を探し - スペースデリミタ
  • 質問なしで二重に構文解析する
C++では

検出されデフォルトの区切り文字の1まで

  • 読む文字をループ。これは実際のデータパターンへの検出を制限するものではありません。それはちょうど場合により多くのデリミタを確認します。オーバーヘッドどこでも。
  • デリミタが見つかると、蓄積された文字列を正常に解析しようとします。それはあなたのデータのパターンを仮定しません。たとえば、800文字の連続した数字があり、そのタイプの候補ではない場合は、その可能性を単独で検出できる必要があるため、それにいくらかのオーバーヘッドが追加されます。

私が提案しているパフォーマンスを向上させる1つの方法は、上記のコメントでPeterが言ったことに非常に近いものです。 getlineoperator>>の中に入れて、あなたのデータについて知ることができます。このような何かが、戻ってあなたの速度のいくつかを与えることができる必要があり、それがバックC-INGのあなたのコードの一部のように何らかの形だと思った:

istream &operator>>(istream &in, point &p) { 
    char bufX[10], bufY[10]; 
    in.getline(bufX, sizeof(bufX), ' '); 
    in.getline(bufY, sizeof(bufY), '\n'); 
    p.x = atof(bufX); 
    p.y = atof(bufY); 
    return in; 
} 

それは便利だと思います。

編集:コメントで述べたようにがnneonneoさんのコメント

+0

Nit: 'in.getline(bufX、sizeof(bufX)、 '');'が必要です。また、 'bufY'には' \ n'を使います。 – nneonneo

+0

@nneonneo入力のおかげで、私はそれに応じて更新しました –

+1

誰かが私の上に積み重ねられたように、私はベンチマークのiostreamsをしようとしていたか、あるいは私は2つのプログラムが同等であったと言いましたが、 CのプログラムとそれをC++のプログラムと照合しようとしていました。私はあなたのコードが実際に動作し、あなたが私に無関係のアドバイスをあまりにも多く与えていないので、受け入れてアップボーディングしています。あなたが実際に私の質問を読んだかのようです。しかし、この全体的な経験は圧倒的に否定的でした。私は何をすべきか分かりません。 –

1

を適用し、入力を読み取るための実際のアルゴリズムは、CのようにC++のように良いですし、あなたが のstd :: IOSを持っていることを確認してください確認してください: :sync_with_stdio(false) したがって、iostreamsはC stdioとの同期をとって減速しません。

私の経験では、C stdioはC++のiostreamsより高速ですが、C libは型保証されておらず拡張性がありません。

+0

['std :: ios :: sync_with_stdio'](http://en.cppreference.com/w/cpp/io/ios_base/sync_with_stdio)は、標準入出力ストリームのみに影響します( 'std :: cout/stdout'のように。 – MikeMB

+0

はい、trueです。sync_with_stdioについて知っていますが、プログラムがstdinから読み込むように変更されない場合は、同期を考慮する必要はありません。 –

+0

これは私が実際に尋ねた質問 –

3

アップデート:私はもう少しテストを行いました(十分なメモリがあれば)VS2015を搭載した私のマシンでは、少なくとも驚くほど簡単な解決策があります。 。

ifstream input("biginput.txt"); 
std::stringstream buffer; 
buffer << input.rdbuf(); 
point p; 
while (buffer >> p) { 
    i++ 
} 

ので問題はないので、多くの関連するC++ストリーミングメカニズム自体に、しかし特にifstreamの内部にあると思われます。ここで


私の元(旧式)回答されています @Frederikはすでにパフォーマンスの不一致が(少なくとも部分的)機能の違いに結びついていることを、説明しました。

性能を回復する方法:VS2015を搭載したマシンでは、Cソリューションが要求する時間の約2/3が次の時間に実行されます(ただし、マシンでは3倍のパフォーマンスギャップ始まるためにあなたの元のバージョンの間):

istream &operator >> (istream &in, point &p) { 
    thread_local std::stringstream ss; 
    thread_local std::string s; 

    if (std::getline(in, s)) { 
     ss.str(s); 
     ss >> p.x >> p.y; 
    } 
    return in; 
} 

私はthread_local変数についてあまりにも満足していないが、彼らは繰り返し動的なメモリ割り当てのオーバーヘッドを排除するために必要です。

+0

ここで助けようとしています(そして、私は特に上記のコメントにあなたの助けに感謝します)が、これはハックにハッキングを重ねるように私に感じます。私はWindowsでギャップが狭いと聞いて驚いています。あなたが実際に私の質問に答えることを試みているので、アップ投票。 –

関連する問題