Modern Arm Assembly Language Programming: Covers Armv8-A 32-bit, 64-bit, and SIMD
by Daniel Kusswurm
2021.07.28: updated by
Up
Chapter 17: Optimization Strategies and Techniques
Armv8 Microarchitecture
プロセッサのマイクロアーキテクチャは、命令フェッチバイプライン、
命令デコーダ、
実行パイプライン、
メモリキャッシュなど、
ハードウェア構成要素の編成と操作によって特徴づけられる。
Cortex Microarchitecture
Cortex-A72 クアッドコアプロセッサは、各プロセッサコアには
48KB L1 命令キャッシュと、32KB L1データキャッシュが含まれている。
4個のプロセッサコアは、512KB -4MBのL2統合キャッシュを共有している。
Cortex-A77 クアッドコアプロセッサは、各プロセッサコアには
64KB L1 命令キャッシュ、64KB L1データキャッシュ、
および 256KB - 512KB の L2キャッシュが含まれている。
4個のプロセッサコアは、512KB -4MBのL3統合キャッシュを共有している。
Cortex Front-End Pipeline
Arm Cortex プロセッサコアは、
fetch, decode, dispatch, execute, retire
の5段階のパイプライン動作を行う。
Cortex-A72 プロセッサでは、branch prediction, instruction fetch, decode rename dispatch
は In Order で行われる。
Issue および Execution は Out of order で実行される。
decode unit は、アセンブリ言語の命令コードをマイクロオペレーションに変換する。
生成されるマイクロオペレーションの数は、元の命令によって異なる。
Cortex-A77 プロセッサも、Cortex-A72 と同様のパイプラインから構成されているが、
execution unit の数が増えている。
パフォーマンスが大幅に向上し、消費電流も削減されている。
Cortex Execution Pipelines
Cortex-A72 とCortex-A77 プロセッサはどちらも2つの floating-point/SIMD 実行パイプラインを持っており、
並列に実行できる。
Optimizing Armv8 Assembly Language Code
Basic Techniques
- 特にforループでは、ldr疑似命令(例:ldr r0,=Val1 または ldr x0、=Val1)の使用を最小限に抑える。
mov / movw / movt(A32)またはmov / movz / movk(A64)命令(またはこれらの命令の中断されないシーケンス)を使用して、
アドレスまたは定数値をレジスターにロードする。
- condition flag の結果が必要とならない場合は、条件フラグを設定しない命令を使う(例 adds x2, x1, x0 ではなく add x2, x1, x0 を使う)。
- read onlyなconstant, table, arrays, text string などのデータはコード領域 (.text section) に置くこと。関数の実行コードの前か後ろにおけばよい。
- 大きなread only table, array, text string などのデータはデータ領域 (.data section) に配置すること。
L1命令キャッシュから命令コードがflushされるのを避けるため。
- ldr命令のリテラル形式(たとえば、ldr x0、label) を使用して、相対位置を指定して定数を読み込むこと。
A32の場合は ±4095 byte、A64の場合は ±1M byteの相対位置が指定できる。
- 頻繁に使う定数や中間値はレジスタに保存する。
- 汎用レジスタの値を一時的に退避する場合は、メモリよりも SIMD レジスタを使う方が速度が速い。
- 連続する要素の小さなグループを使って配列や行列を処理することにより、キャッシュデータが効率的に使われるようにする。
Floating-Point Arithmetic
- 倍精度浮動小数点値の代わりにできるだけ単精度を使用する。
- 浮動小数点計算を含むforループ(特に浮動小数点の加算、乗算、または乗算加算演算)は unroll (展開)する。
- 非正規化 floating-point 定数を使わない。
- 計算により非正規化 floating-point number が多く発生する場合は、 flush-to-zero mode を有効にする。
Branch Instructions
条件付き分岐命令は、パイプラインと内部キャッシュの影響により、実行に時間がかかる可能性がある。
次の最適化手法を使用して、分岐命令のパフォーマンスへの悪影響を最小限に抑え、分岐予測ユニットの精度を向上させる。
- 実行される分岐命令の数が最小になるようにコードを書く。
- 短い処理ループを展開して、実行される条件分岐命令の数を減らす。
ただし、過度のループ展開は実行が遅くなる可能性があるので注意する。
- A32コードでは、conditional instruction form (条件付き命令フォーム, addeq、subneなど)を使用して、
データに依存するブランチを減らす。
条件付き命令フォームの使用は、低レイテンシの整数命令(add、sub、and、orr、eorなど)、ロード命令、およびストア命令のみとする。
- A64コードでは、可能な場合は条件分岐命令ではなく、csel、cset、およびfcsel命令の使用を優先します。
- 頻繁に呼び出される関数と分岐ターゲットを、パフォーマンスが重要なforループ内の16バイト境界に配置します。
- 16バイトに整列された 同じ quad-word コードメモリの中で3個以上の branch 命令は使わない。
- 実行される可能性が低いコード(エラー処理コードなど)は別のプログラムセクションまたは別のメモリページに置く。
Data Alignment
- multibyte integer integer と floating-point number は、それぞれのデータの natural boundaries に配置する。
- 64 bit および 128 bit 幅の packed integer や packed floating-point number をそれぞれのデータの natural boundaries に配置する。
- 16 byte 境界にまたがるメモリオペランドを持つ store 命令は使用しない。
- 4 byte 境界に整列されていないメモリオペランドからの quad load 命令は使用しない。
- cache lineの境界にまたがるメモリオペランドからのロード命令は使用しない。
Cortex A-7X プロセッサファミリは、64 byte幅のcache line を持つ。
- データ構造をパディングして、各構造体メンバーを適切な align に配置する。
- データ構造の中の小さな配列や短いテキスト文字列を適切な align に配置して、キャッシュ line の分割(cache line の境界をまたいでデータをアクセスするときに発生する)を避ける。
- さまざまなデータ構造を試す(たとえば、配列の構造と構造の配列)。
SIMD Techniques
- packed integer や packed floating-point の計算を含む for-loop を unroll (展開) する。
- レジスタの依存関係を最小限に抑えて、複数の実行パイプラインを活用する。
- 頻繁に使用されるメモリオペランドや packedパ 定数をSIMDレジスタにロードして保持する。
- 連続する要素の小さなグループを使って配列や行列を処理することにより、キャッシュデータが効率的に使われるようにする。
Summary
http://ynitta.com/