6

一部の人々は、「配列の添え字によって実現できる操作はすべて、ポインターでも実行できます。一般に、ポインター バージョンの方が高速です」と述べています。

上記の結果に疑問があるので、次のテストを行います:</p>

次の記事では、コンパイラの最適化は気にしません。コンパイラーの最適化について、ポインターと配列の間の効率に影響を与える方法については、次の点に注意してください:効率: 配列とポインター

(Visual Studio 2010、デバッグ モード、最適化なし)

#include <windows.h>
#include <stdio.h>

int main()
{
    int a[] = {10,20,30};
    int* ap = a;

    long counter;

    int start_time, end_time;
    int index;

    start_time = GetTickCount();
    for (counter = 1000000000L; counter>0; counter--)
    {
        *(ap+1) = 100;
    }
    end_time = GetTickCount();
    printf("10 billion times of *ap = %d\n", end_time-start_time);

    start_time = GetTickCount();
    for (counter = 1000000000L; counter>0; counter--)
    {
        a[1] = 101;
    }
    end_time = GetTickCount();
    printf("10 billion times of a[0] = %d\n", end_time-start_time);

    return 0;
}

結果は:</p>

10 billion times of *ap = 3276
10 billion times of a[0] = 3541

ポインタは少し速いようです。しかし、逆アセンブルを比較した後、私はより深い混乱に陥りました。</p>

(Visual Studio 2010、デバッグ モード、最適化なし)

; 17   :         *(ap+1) = 100;
mov eax, DWORD PTR _ap$[ebp]
mov DWORD PTR [eax+4], 100          ; 00000064H

; 25   :         a[1] = 101;
mov DWORD PTR _a$[ebp+4], 101       ; 00000065H

アセンブル出力から、ポインタを介したメモリ アクセスには 2 命令、配列には 1 命令しかかかりません。

なぜ配列は少ない命令を実行するのに、ポインターよりも時間がかからないのですか?

CPUキャッシュに関連付けられていますか?それを証明するためにテスト コードを変更するにはどうすればよいですか?

4

1 に答える 1

2

まず、最も重要なこととして、C 言語には速度がありません。これは、C の実装によって導入された属性です。たとえば、C には速度がありませんが、GCC コンパイラは、Clang コンパイラによって生成されるコードとは速度が異なる可能性があるコードを生成します。 Cint または Ch インタープリターによって生成された動作を実行します。これらはすべて C の実装です。それらのいくつかは他のものよりも遅いですが、とにかくその速度は C に起因するものではありません!

C標準の6.3.2.1は次のように述べています。

sizeof 演算子、_Alignof 演算子、または単項 & 演算子のオペランドである場合、または配列の初期化に使用される文字列リテラルである場合を除き、''array of type'' 型の式は次の式に変換されます。配列オブジェクトの最初の要素を指し、左辺値ではない型「型へのポインタ」。

これは、コード内の と の両方が*(ap+1)ポインタ操作であることを示しているはずです。この変換は、Visual Studio でのコンパイル フェーズ中に行われます。したがって、これがランタイムに影響を与えることはありません。a[1]

「配列添字」に関する6.5.2.1は次のように述べています。

式の 1 つは型「完全なオブジェクト型へのポインター」を持ち、もう 1 つの式は整数型を持ち、結果は型「型」を持ちます。これは、配列添字演算子が実際にはポインター演算子であることを示しているようです...

これは、以前に仮定したように、ap[1]実際にポインター操作であることの確認です。ただし、実行時には、配列はすでにポインターに変換されています。パフォーマンスは同一である必要があります。

...では、なぜそれらは同一ではないのでしょうか?

使用しているOSの特徴は?マルチタスク、マルチユーザー OS ではないですか? OS が中断することなく最初のループを完了し、その後 2 番目のループを中断して別のプロセスに制御を切り替えるとします。この中断はあなたの実験を無効にしませんか? タスク切り替えによる中断の頻度とタイミングをどのように測定しますか? これは OS によって異なり、OS は実装の一部であることに注意してください。

使用しているCPUの特徴は?マシンコード用の独自の高速内部キャッシュはありますか? 最初のループ全体と、その包括的なタイミング メカニズムがコード キャッシュにうまく収まるはずでしたが、2 番目のループが切り捨てられたとします。これにより、キャッシュ ミスが発生し、CPU が RAM から残りのコードをフェッチするまで長い待ち時間が発生しませんか? キャッシュミスによる中断のタイミングをどのように測定しますか? これは CPU によって異なり、CPU は実装の一部であることに注意してください。

これらの質問は、「このマイクロ最適化ベンチマークは意味のある、または重要な問題を解決しますか?」などの質問を提起する必要があります。最適化の成功は、問題のサイズと複雑さによって異なります。重要な問題を見つけて解決し、ソリューションをプロファイリングし、最適化し、再びプロファイリングします。こうすることで、最適化されたバージョンがどれだけ高速であるかについて、意味のある情報を提供できます。先に述べたように、最適化がおそらくあなたの実装にのみ関連していることをあなたが漏らさない限り、あなたの上司はあなたに大いに満足するでしょう. 配列の逆参照とポインタの逆参照が最も少ない心配であることがわかると思います。

于 2013-04-05T15:13:02.470 に答える