一次元配列の総和を求めるプログラムを紹介します。以降に処理の概要図を示します。
64ビット整数型
呼び出し側・符号付
以降に、呼び出し側のC++コードを示します。common.hをincludeしていますがSIMDで記述したアセンブリ言語の関数と等価な機能を持つC++言語で記述した共通関数のヘッダです。最下部にソースコードを示します。条件は関数名に含まれます。
#include "..\common.h" // TEMPLATES #define T short extern "C" T aaddw(const T*, const size_t); // main int main(void) { const size_t ArrLen = 4096; static_assert(ArrLen % 16 == 0, "number of elements must be an integral multiple of 16."); T a[ArrLen]; init(a, ArrLen); T cr = cadd (a, ArrLen); T ar = aaddw(a, ArrLen); cout << fixed << setprecision(2); cout << "cpp = " << cr << endl; cout << "asm = " << ar << endl; return 0; }
一次元配列にランダムな値を与え、先ほどの関数を呼び出し一次元配列の総和を求めます。
呼び出され側
本ライブラリへは、入力配列 a のアドレスが rcx レジスターへ、配列の要素数がrdx レジスターへ渡されます。渡される内容とレジスターの対応を表で示します。
レジスタ | 内容 |
---|---|
rcx | 入力配列の先頭アドレス |
rdx | 配列の要素数 |
_TEXT segment public aaddq align 16 aaddq proc vpxorq zmm0, zmm0, zmm0 ; zmm0 = 0 xor rax, rax ; clear index loop_f: vpaddq zmm0, zmm0, zmmword ptr [rcx+rax*8] add rax, 8 cmp rax, rdx jb short loop_f ;loop loop_f vextracti64x4 ymm1, zmm0, 1 ; reduce zmm0 vpaddq ymm1, ymm0, ymm1 vextracti64x2 xmm0, ymm1, 1 vpaddq xmm2, xmm0, xmm1 vpsrldq xmm0, xmm2, 8 vpaddq xmm1, xmm0, xmm2 vmovq rax, xmm1 ret aaddq endp _TEXT ends end
まず、8要素単位の総和を保持するzmm0レジスターをvpxorq命令で0へ初期化します。次に配列を参照するインデックスを保持するraxレジスターをxor命令でクリアします。vpaddq命令で、zmm0レジスターと対象となる配列の8要素を加算し、結果をzmm0レジスターへ格納します。処理対象要素は、先頭アドレスを保持するrcxレジスターへインデックスを保持するraxレジスターへ8を乗じたものを加算して求めます。
次に、インデックスを保持するraxレジスターへ8を加算し、これを配列の要素数を保持するrdxレジスターと比較し、すべての要素を処理したか調べます。処理が終わっていなければ、次の要素の処理へ移ります。
全要素の処理が完了するとzmm0レジスターへ8要素単位の総和が格納されています。この8要素の総和を求めるreduce処理を行い、配列全体の総和を求めます。言葉で説明すると分かりづらいため、zmm0レジスターに格納された8要素単位の最小値から、全体の最小値を求める方法を図で示します。
これまでにreduceの説明はいくつか出てきていますので、詳細な説明は紙面を節約するために省きます。図とソースコードを参照することで処理は理解できるでしょう。
common.h、共通関数
ヘッダーファイルの一部を示します。簡単な内容なので、説明は省きします。
: // C++の関数名にはデータ型を含ませない template <typename T> T cadd(const T* a, const size_t length) { T sum = 0; for (size_t i = 0; i < length; i++) { sum += a[i]; } return sum; } :
すべての型に対応させるため、テンプレート関数とします。cadd 関数は一次元配列の全要素を加算した総和を返します。
このプログラムの実行例を示します。
実行結果(signed)
C:\>ml64 /c addAsm.asm
…
C:\>cl /O2 /EHsc add.cpp addAsm.obj
…
C:\>add
cpp = 3747339
asm = 3747339
正常に処理されているのが分かります。