2

32 ビット CPU モード以降、x86 アーキテクチャで使用できる拡張アドレス オペランドがあります。ベースアドレス、ディスプレースメント、インデックスレジスタ、スケーリングファクタを指定できます。

たとえば、32 ビット整数のリスト (32 バイト長のデータ構造の配列の最初の 2 つ%rdiをデータ インデックス%rbxとして、ベース ポインターとして) をストライドしたいとします。

addl   $8, %rdi                # skip eight values: advance index by 8
movl   (%rbx, %rdi, 4), %eax   # load data: pointer + scaled index
movl   4(%rbx, %rdi, 4), %edx  # load data: pointer + scaled index + displacement

私が知っているように、このような複雑なアドレス指定は、1 つの機械語命令に収まります。しかし、そのような操作のコストはいくらで、独立したポインター計算による単純なアドレス指定と比較してどうですか?

addl  $32, %rbx      # skip eight values: move pointer forward by 32 bytes
movl  (%rbx), %eax   # load data: pointer
addl  $4, %rbx       # point next value: move pointer forward by 4 bytes
movl  (%rbx), %edx   # load data: pointer

後者の例では、1 つの追加の命令と依存関係を導入しました。しかし、整数の加算は非常に高速で、より単純なアドレス オペランドが得られ、乗算はもうありません。一方、許可されている倍率は 2 のべき乗であるため、乗算はビット シフトに帰着します。これも非常に高速な操作です。それでも、2 回の加算とビット シフトを 1 回の加算に置き換えることができます。

これら 2 つのアプローチのパフォーマンスとコード サイズの違いは何ですか? 拡張アドレッシング オペランドを使用するためのベスト プラクティスはありますか?

または、C プログラマーの観点から尋ねると、配列のインデックス付けとポインター演算のどちらが高速ですか?


サイズ/パフォーマンス調整用のアセンブリ エディタはありますか? 各アセンブリ命令のマシン コード サイズ、クロック サイクル単位の実行時間、または依存関係グラフを確認できればと思います。このようなアプリケーションの恩恵を受けるアセンブリ フリークは何千人もいるので、このようなアプリケーションが既に存在していることは間違いありません。

4

2 に答える 2

2

あなたの質問への答えは、特定のローカル プログラム フローの状況によって異なります。また、プロセッサの製造元とアーキテクチャの間で多少異なる場合があります。1 つまたは 2 つの命令をマイクロ分析しても、通常は無意味です。マルチステージ パイプライン、複数の整数ユニット、キャッシュ、その他多くの機能があり、分析に組み込む必要があります。

生成されたアセンブリ コードを調べて、シーケンスを処理するさまざまなハードウェア ユニットに関してシーケンスがそのように見える理由を分析することで、リバース エンジニアリングを試すことができます。

もう 1 つの方法は、プロファイラーを使用してさまざまな構造を試し、何がうまく機能し、何がうまくいかないかを確認することです。

また、gcc のソース コードをダウンロードして、非常にクールなプログラマーがシーケンスを評価して最速のコードを生成する方法を確認することもできます。いつの日か、あなたもそのうちの 1 人になるかもしれません :-)

いずれにせよ、最適なシーケンスは、プロセッサ、コンパイラ、最適化レベル、および周囲の命令によって大きく異なるという結論に達すると思います。C を使用している場合、ソース コードの品質は非常に重要です。ガベージ イン = ガベージ アウトです。

于 2013-09-02T07:39:40.100 に答える
2

アドレス演算は非常に高速であるため、可能であれば常に使用する必要があります。

しかし、ここで質問が見逃していることがあります。

最初は、アドレス演算を使用して 32 を掛けることはできません。8 が可能な最大定数です。

コードの最初のバージョンは完全ではありません。インクリメントするための 2 番目の命令が必要になるためrbxです。したがって、次の 2 つのバリアントがあります。

inc  rbx          
mov  eax, [8*rbx+rdi]

add  rbx, 8
mov  eax, [rbx]

このようにして、2 つのバリアントの速度は同じになります。サイズは同じで、6 バイトです。

したがって、どのコードが優れているかは、プログラム コンテキストのみに依存します。必要な配列セルのアドレスが既に含まれているレジスタがある場合は、mov eax, [rbx] を使用します。

セルのインデックスを含むレジスタと、開始アドレスを含む別のレジスタがある場合は、最初のバリアントを使用します。このようにして、アルゴリズムが終了した後も、配列の開始アドレスが rdi に残っています。

于 2013-09-02T06:49:58.423 に答える