11

C++ はすべての点で C と同じかそれよりも高速ですが、よりクリーンで優れていると多くの人が言っているのをここで聞いたことがあります。

C++ が非常に洗練されており、非常に高速であるという事実に矛盾はありませんが、重要なメモリ アクセスやプロセッサに依存するアプリケーションに代わるものは見つかりませんでした。

質問: パフォーマンスの観点から、C スタイルの配列に相当するものは C++ にありますか?

以下の例はでたらめですが、実際の問題の解決策に興味があります。私は画像処理アプリを開発していますが、そこでのピクセル処理の量は膨大です。

double t;

// C++ 
std::vector<int> v;
v.resize(1000000,1);
int i, j, count = 0, size = v.size();

t = (double)getTickCount();

for(j=0;j<1000;j++)
{
    count = 0;
    for(i=0;i<size;i++)
         count += v[i];     
}

t = ((double)getTickCount() - t)/getTickFrequency();
std::cout << "(C++) For loop time [s]: " << t/1.0 << std::endl;
std::cout << count << std::endl;

// C-style

#define ARR_SIZE 1000000

int* arr = (int*)malloc( ARR_SIZE * sizeof(int) );

int ci, cj, ccount = 0, csize = ARR_SIZE;

for(ci=0;ci<csize;ci++)
    arr[ci] = 1;

t = (double)getTickCount();

for(cj=0;cj<1000;cj++)
{
    ccount = 0;
    for(ci=0;ci<csize;ci++)
        ccount += arr[ci];      
}

free(arr);

t = ((double)getTickCount() - t)/getTickFrequency();
std::cout << "(C) For loop time [s]: " << t/1.0 << std::endl;
std::cout << ccount << std::endl;

結果は次のとおりです。

(C++) For loop time [s]: 0.329069

(C) For loop time [s]: 0.229961

注:getTickCount()サードパーティのライブラリからのものです。テストしたい場合は、お気に入りのクロック測定値に置き換えるだけです

アップデート:

私はVS 2010、リリースモードを使用しています。それ以外はすべてデフォルトです

4

6 に答える 6

12

簡単な答え: ベンチマークに欠陥があります。

より長い答え: C++ のパフォーマンスを向上させるには、完全な最適化を有効にする必要があります。それでも、あなたのベンチマークにはまだ欠陥があります。

いくつかの観察:

  1. 完全最適化をオンにすると、for ループの非常に大きなチャンクが削除されます。これにより、ベンチマークが無意味になります。
  2. std::vector動的再割り当てのオーバーヘッドがあります。試してみてくださいstd::array
    具体的には、Microsoft の stl はデフォルトでイテレータをチェックしています。
  3. C / C++ コード / ベンチマーク コード間のクロス リオーダーを防止する障壁はありません。
  4. (実際には関係ありません)cout << ccountロケールを認識していますが、そうでprintfはありません。 std::endl出力をフラッシュしますprintf("\n")。しないでください。

C++ の利点を示す「従来の」コードは C qsort()vs C++std::sort()です。ここでコードのインライン化が役立ちます。

「実際の」アプリケーションの例が必要な場合。レイトレーサーまたは行列乗算のものを検索してください。自動ベクトル化を行うコンパイラを選択します。

更新LLVM オンライン デモ を使用すると、ループ全体が並べ替えられていることがわかります。ベンチマーク コードは開始位置に移動され、分岐予測を向上させるために最初のループのループ終了ポイントにジャンプします。

(これは C++ コードです)

######### jump to the loop end
    jg  .LBB0_11
.LBB0_3:                                # %..split_crit_edge
.Ltmp2:
# print the benchmark result
    movl    $0, 12(%esp)
    movl    $25, 8(%esp)
    movl    $.L.str, 4(%esp)
    movl    std::cout, (%esp)
    calll   std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
.Ltmp3:
# BB#4:                                 # %_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc.exit
.Ltmp4:
    movl    std::cout, (%esp)
    calll   std::basic_ostream<char, std::char_traits<char> >& std::basic_ostream<char, std::char_traits<char> >::_M_insert<double>(double)
.Ltmp5:
# BB#5:                                 # %_ZNSolsEd.exit
    movl    %eax, %ecx
    movl    %ecx, 28(%esp)          # 4-byte Spill
    movl    (%ecx), %eax
    movl    -24(%eax), %eax
    movl    240(%eax,%ecx), %ebp
    testl   %ebp, %ebp
    jne .LBB0_7
# BB#6:
.Ltmp52:
    calll   std::__throw_bad_cast()
.Ltmp53:
.LBB0_7:                                # %.noexc41
    cmpb    $0, 28(%ebp)
    je  .LBB0_15
# BB#8:
    movb    39(%ebp), %al
    jmp .LBB0_21
    .align  16, 0x90
.LBB0_9:                                #   Parent Loop BB0_11 Depth=1
                                        # =>  This Inner Loop Header: Depth=2
    addl    (%edi,%edx,4), %ebx
    addl    $1, %edx
    adcl    $0, %esi
    cmpl    %ecx, %edx
    jne .LBB0_9
# BB#10:                                #   in Loop: Header=BB0_11 Depth=1
    incl    %eax
    cmpl    $1000, %eax             # imm = 0x3E8
######### jump back to the print benchmark code
    je  .LBB0_3

私のテストコード:

std::vector<int> v;
v.resize(1000000,1);
int i, j, count = 0, size = v.size();

for(j=0;j<1000;j++)
{
    count = 0;
    for(i=0;i<size;i++)
         count += v[i];     
}

std::cout << "(C++) For loop time [s]: " << t/1.0 << std::endl;
std::cout << count << std::endl;
于 2012-08-29T08:19:34.043 に答える
12

質問: パフォーマンスの観点から、C スタイルの配列に相当するものは C++ にありますか?

答え: C++ コードを書きましょう! 自分の言語を知り、標準ライブラリを知り、それを使用してください。標準アルゴリズムは正しく、読みやすく、高速です (現在のコンパイラで高速に実装するための最良の方法を知っています)。

void testC()
{
    // unchanged
}

void testCpp()
{
    // unchanged initialization

    for(j=0;j<1000;j++)
    {
        // how a C++ programmer accumulates:
        count = std::accumulate(begin(v), end(v), 0);    
    }

    // unchanged output
}

int main()
{
    testC();
    testCpp();
}

出力:

(C) For loop time [ms]: 434.373
1000000
(C++) For loop time [ms]: 419.79
1000000

g++ -O3 -std=c++0xUbuntu のバージョン 4.6.3 でコンパイルされています。

あなたのコードでは、私の出力はあなたのものと似ています。user1202136 は違いについて良い答えを出します...

于 2012-08-29T08:35:26.067 に答える
8

コンパイラの問題のようです。C 配列の場合、コンパイラーはパターンを検出し、自動ベクトル化を使用して SSE 命令を発行します。ベクターの場合、必要なインテリジェンスが不足しているようです。

コンパイラに SSE を使用しないように強制すると、結果は非常に似ています (でテストg++ -mno-mmx -mno-sse -msoft-float -O3):

(C++) For loop time [us]: 604610
1000000
(C) For loop time [us]: 601493
1000000

この出力を生成したコードは次のとおりです。基本的には質問のコードですが、浮動小数点はありません。

#include <iostream>
#include <vector>
#include <sys/time.h>

using namespace std;

long getTickCount()
{
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return tv.tv_sec * 1000000 + tv.tv_usec;
}

int main() {
long t;

// C++ 
std::vector<int> v;
v.resize(1000000,1);
int i, j, count = 0, size = v.size();

t = getTickCount();

for(j=0;j<1000;j++)
{
    count = 0;
    for(i=0;i<size;i++)
         count += v[i];     
}

t = getTickCount() - t;
std::cout << "(C++) For loop time [us]: " << t << std::endl;
std::cout << count << std::endl;

// C-style

#define ARR_SIZE 1000000

int* arr = new int[ARR_SIZE];

int ci, cj, ccount = 0, csize = ARR_SIZE;

for(ci=0;ci<csize;ci++)
    arr[ci] = 1;

t = getTickCount();

for(cj=0;cj<1000;cj++)
{
    ccount = 0;
    for(ci=0;ci<csize;ci++)
        ccount += arr[ci];      
}

delete arr;

t = getTickCount() - t;
std::cout << "(C) For loop time [us]: " << t << std::endl;
std::cout << ccount << std::endl;
}
于 2012-08-29T08:23:50.123 に答える
4

動的にサイズ設定された配列に相当するC++はstd::vector。固定サイズの配列に相当するC++は、std::arrayまたはstd::tr1::arrayC++11より前のバージョンです。

ベクターコードにサイズ変更がない場合、最適化を有効にしてコンパイルすると、動的に割り当てられたC配列を使用するよりも大幅に遅くなる可能性があることを理解するのは困難です。

注:投稿されたとおりにコードを実行し、x86のgcc 4.4.3でコンパイルし、コンパイラオプション

g ++ -Wall -Wextra -pedantic-errors -O2 -std = c ++ 0x

結果は繰り返しに近い

(C ++)ループ時間の場合[us]:507888

1000000

(C)ループ時間の場合[us]:496659

1000000

std::vectorしたがって、少数の試行の後、バリアントの場合は2%遅くなるようです。この互換性のあるパフォーマンスを検討したいと思います。

于 2012-08-29T08:05:25.907 に答える
0

通常、コンパイラはすべての最適化を行います...適切なコンパイラを選択するだけです

于 2012-08-29T08:21:04.630 に答える
0

あなたが指摘するのは、オブジェクトへのアクセスには常に少しのオーバーヘッドが伴うという事実です。そのため、 avectorへのアクセスは古き良き配列へのアクセスよりも高速ではありません。

しかし、配列を使用するのが「C スタイル」であっても、C++ のままなので問題ありません。

次に、@juanchopanza が言ったようにstd::array、C++11 にはあります。これは、より効率的である可能性がありますがstd::vector、固定サイズの配列に特化しています。

于 2012-08-29T08:09:26.740 に答える