この記事は、インテル® デベロッパー・ゾーンに掲載されている「Intel® Compiler 17.0 New Feature: Code Alignment for Loops」(https://software.intel.com/en-us/articles/intelr-compiler-170-new-feature-code-alignment-for-loops) の日本語参考訳です。
インテル® コンパイラーのバージョン 17.0 以降では、ループのコード・アライメントを制御する新しいオプションが追加されました。
シンタックス
Linux* および macOS*: -falign-loops[=n], -fno-align-loops
Windows*: /Qalign-loops[:n], /Qalign-loops-
引数
n は、最小アライメント境界を指定するバイト数です。これは、1 から 4096 の範囲内の 2 の累乗の値でなければなりません (例: 1、2、4、8、16、32、64、128 など)。n = 1 ではアライメントを行いません。n が省略された場合、16 バイトのアライメントが適用されます。
デフォルト: ループの特別なアライメントは行いません。
-fno-align-loops (Linux*) または /Qalign-loops- (Windows*)
このオプションを指定してコンパイルすると、アセンブリーのループの前に “.Align”1 ディレクティブが追加されていることが分かります。例えば、”-falign-loops=64 -qopt-report -g -O2 -xCORE-AVX2″ オプションでコンパイルすると、ベクトル化されたループの直前に “.align 64,0×90” が生成されていることが分かります。
..LN38: vmovapd %ymm0, %ymm9 #39.13 .align 64,0x90 ..LN39: # LOE rax rdx rcx rbx rbp rdi r8 r9 r11 r12 esi r10d r14d xmm8 ymm0 ymm1 ymm2 ymm3 ymm4 ymm5 ymm6 ymm7 ymm9 ..B1.6: # Preds ..B1.6 ..B1.5 # Execution count [2.50e+01] ..L11: #38.7 # optimization report # LOOP WAS UNROLLED BY 8 # LOOP WAS VECTORIZED # VECTORIZATION SPEEDUP COEFFECIENT 3.878906 # VECTOR TRIP COUNT IS ESTIMATED CONSTANT # VECTOR LENGTH 4 # NORMALIZED VECTORIZATION OVERHEAD 0.343750 # MAIN VECTOR TYPE: 64-bits floating point ..LN40: .loc 1 38 is_stmt 1 ..LN41: .loc 1 39 is_stmt 1 vmovupd (%r8,%r12,8), %ymm10 #39.31
より細かな制御
コンパイラー・オプションを使用してループのアライメントを制御すると、プログラム中のすべてのループに影響します。時にはループごとにこの機能を適用することで、良い結果がもたらされることもあります。
シンタックス
C/C++: #pragma code_align(n)
Fortran: !DIR$ CODE_ALIGN [:n]
引数 n は、-falign-loops や /Qalign-loops オプションの引数と同様の意味を持ちます。
このプラグマ (ディレクティブ) は、アライメントするループの前に配置する必要があります。Qalign-loops:m または -falign-loops=m オプションを指定してコードをコンパイルすると、ループの前に code_align:n プラグマ (ディレクティブ) がある場合、そのループは max (m, n) バイト境界でアライメントされます。
C の例:
for (i = 0; i < rows; i += inc_i) { #pragma code_align 64 #pragma vector aligned for (j = 0; j < cols; j += inc_j) { b[i] += a[i][j] * x[j]; } }
Fortran の例:
!DIR$ CODE_ALIGN :64 do i = 1, cols, inc_i do j = 1, rows, inc_j b(i) = b(i) + a(j, i) * x(j) enddo enddo
推奨される使用法
現代のインテル® プロセッサーに搭載されている「ループストリーム検出器(LCD)2」は、小さなループ本体のループ・アライメントから恩恵を得られます。上記の例のように、デフォルトオプション -mSSE2 が指定されるコンパイルでは、コンパイラーは最適化のため小さなループに対して LSD に完全に収まるように align ディレクティブを生成します。
LSD サイズよりもはるかに小さなループでは、入れ子になったループがある場合、ループ・アライメント・オプションを使用するかプラグマやディレクティブによりアライメントを調整することで、さらにパフォーマンスが向上する可能性があります。
例えば、次ループの内部ループは 10 命令未満で構成されます。外部ループに続いてループ・アライメントを追加することで、パフォーマンスが向上します。
do n=1,100 !DIR$ CODE_ALIGN :16 do i=1,100000,10 a(:,i) = b(:,i) + i enddo enddo
インテル® 64 および IA-32 アーキテクチャー・ソフトウェア開発マニュアル (英語) には、次のように記述されています。
「状況に応じてループキャッシュ機能を使用します。パフォーマンスが高いコードを作成するには、LSD 機能をオーバーフローしても、通常はループアンロールを行う方がパフォーマンス的に望ましい傾向があります。」
参考文献
1. x86 アセンブリーの align ディレクティブ: http://web.mit.edu/gnu/doc/html/as_7.html (英語)
2. インテル® 64 および IA-32 アーキテクチャー・ソフトウェア開発マニュアル (英語)
コンパイラーの最適化に関する詳細は、最適化に関する注意事項を参照してください。