3

これらのうち、計算効率が高いのはどれですか? また、その理由は何ですか?

A) 配列アクセスの繰り返し:

for(i=0; i<numbers.length; i++) {
    result[i] = numbers[i] * numbers[i] * numbers[i];
}

B) ローカル変数の設定:

for(i=0; i<numbers.length; i++) {
    int n = numbers[i];
    result[i] = n * n * n;
}

繰り返される配列アクセスのバージョンを (ポインター演算を使用して) 計算する必要があるのではないでしょうか?

for(i=0; i<numbers.length; i++) {
    result[i] = *(numbers + i) * *(numbers + i) * *(numbers + i);
}
4

3 に答える 3

12

十分に洗練されたコンパイラは、3 つのソリューションすべてに対して同じコードを生成します。私はあなたの 3 つのバージョンを小さな C プログラムに変えました (少し調整してnumbers.length、配列の長さを与えるマクロ呼び出しへのアクセスを変更しました):

#include <stddef.h>

size_t i;
static const int numbers[] = { 0, 1, 2, 4, 5, 6, 7, 8, 9 };

#define ARRAYLEN(x) (sizeof((x)) / sizeof(*(x)))
static int result[ARRAYLEN(numbers)];

void versionA(void)
{
    for(i=0; i<ARRAYLEN(numbers); i++) {
        result[i] = numbers[i] * numbers[i] * numbers[i];
    }
}

void versionB(void)
{
    for(i=0; i<ARRAYLEN(numbers); i++) {
        int n = numbers[i];
        result[i] = n * n * n;
    }
}

void versionC(void)
{
    for(i=0; i<ARRAYLEN(numbers); i++) {
            result[i] = *(numbers + i) * *(numbers + i) * *(numbers + i);
    }
}

次に、Visual Studio 2012 で最適化を使用して (およびデバッグ シンボルを使用して、逆アセンブルをより適切に) コンパイルしました。

C:\Temp>cl /Zi /O2 /Wall /c so19244189.c
Microsoft (R) C/C++ Optimizing Compiler Version 17.00.50727.1 for x86
Copyright (C) Microsoft Corporation.  All rights reserved.

so19244189.c

最後に、分解は次のとおりです。

C:\Temp>dumpbin /disasm so19244189.obj

[..]

_versionA:
  00000000: 33 C0              xor         eax,eax
  00000002: 8B 0C 85 00 00 00  mov         ecx,dword ptr _numbers[eax*4]
            00
  00000009: 8B D1              mov         edx,ecx
  0000000B: 0F AF D1           imul        edx,ecx
  0000000E: 0F AF D1           imul        edx,ecx
  00000011: 89 14 85 00 00 00  mov         dword ptr _result[eax*4],edx
            00
  00000018: 40                 inc         eax
  00000019: 83 F8 09           cmp         eax,9
  0000001C: 72 E4              jb          00000002
  0000001E: A3 00 00 00 00     mov         dword ptr [_i],eax
  00000023: C3                 ret

_versionB:
  00000000: 33 C0              xor         eax,eax
  00000002: 8B 0C 85 00 00 00  mov         ecx,dword ptr _numbers[eax*4]
            00
  00000009: 8B D1              mov         edx,ecx
  0000000B: 0F AF D1           imul        edx,ecx
  0000000E: 0F AF D1           imul        edx,ecx
  00000011: 89 14 85 00 00 00  mov         dword ptr _result[eax*4],edx
            00
  00000018: 40                 inc         eax
  00000019: 83 F8 09           cmp         eax,9
  0000001C: 72 E4              jb          00000002
  0000001E: A3 00 00 00 00     mov         dword ptr [_i],eax
  00000023: C3                 ret

_versionC:
  00000000: 33 C0              xor         eax,eax
  00000002: 8B 0C 85 00 00 00  mov         ecx,dword ptr _numbers[eax*4]
            00
  00000009: 8B D1              mov         edx,ecx
  0000000B: 0F AF D1           imul        edx,ecx
  0000000E: 0F AF D1           imul        edx,ecx
  00000011: 89 14 85 00 00 00  mov         dword ptr _result[eax*4],edx
            00
  00000018: 40                 inc         eax
  00000019: 83 F8 09           cmp         eax,9
  0000001C: 72 E4              jb          00000002
  0000001E: A3 00 00 00 00     mov         dword ptr [_i],eax
  00000023: C3                 ret

アセンブリがすべての場合でまったく同じであることに注意してください。だからあなたの質問に対する正しい答え

これらのうち、計算効率が高いのはどれですか? また、その理由は何ですか?

このコンパイラの場合: mu。あなたの質問は、誤った仮定に基づいているため、回答できません。どの答えも他のどの答えよりも速いわけではありません。

于 2013-10-08T10:02:53.617 に答える
2

理論上の答え:

適度に優れた最適化コンパイラは、バージョン A をバージョン B に変換し、メモリからの読み込みを 1 回だけ実行する必要があります。最適化が有効になっている場合、パフォーマンスの違いはありません。

最適化が無効になっている場合、バージョン A は遅くなります。これは、アドレスを 3 回計算する必要があり、メモリの読み込みが 3 回あるためです (そのうちの 2 つはキャッシュされ、非常に高速ですが、レジスタを再利用するよりも低速です)。

実際には、答えはコンパイラによって異なり、ベンチマークでこれを確認する必要があります。

于 2013-10-08T09:59:15.143 に答える
1

コンパイラによって異なりますが、すべて同じである必要があります。

最初に、Bスマート コンパイラが値をレジスタに 1 回だけロードするコードを生成する場合を見てみましょう。そのため、追加の変数を使用するかどうかは関係ありません。コンパイラは命令のオペコードを生成し、値をレジスタに格納しますmovBと同じですA

Aと を比較してみましょうC[]オペレーターのインライン実装を検討する必要があります。a[b]実際には、ケースとは同じである*(a + b)ことを意味するのと同じです。*(numbers + i)numbers[i]AC

つまり、私が何を意味するかを知っていれば(A==B) && (A==C)、すべてが揃ってい(A==B==C)ます:)。

于 2013-10-08T10:11:08.270 に答える