一次元配列の各要素を検査し、与えられた値と一致する要素のインデックスを抽出します。戻り値は一次元配列に存在する特定値の数です。
符号付64ビット整数型
呼び出し側・符号付
以降に、呼び出し側のC++コードを示します。一次元配列から指定された値のインデックスと、その数を取得します。
common.hをincludeしていますがSIMDで記述したアセンブリ言語の関数と等価な機能を持つC++言語で記述した共通関数のヘッダです。最下部にソースコードを示します。条件は関数名に含まれます。
#include "..\common.h" // TEMPLATES #define T long long extern "C" { int aextIdxSQ(const T*, int*, const T, const size_t); } 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]; int cIdx[ArrLen], aIdx[ArrLen]; const T cmpValue = -10; cout << "cmpValue = " << cmpValue << endl; init(a, ArrLen); int cLen = cextIdx (a, cIdx, cmpValue, ArrLen); int aLen = aextIdxSQ(a, aIdx, cmpValue, ArrLen); verify(cLen, aLen, cIdx, aIdx, ArrLen); return 0; }
簡単な処理ですので説明は省きます。
呼び出され側
本関数へ渡される内容とレジスターの対応を表で示します。
レジスタ | 内容 |
---|---|
rcx | 入力配列 a の先頭アドレス |
rdx | インデックスを格納する配列 vIdx の先頭アドレス |
r8 | 特定値 |
r9 | 入力配列 a の要素数 |
64ビット整数型配列の要素が指定された値と一致するか調べ、そのインデックスを抽出します。また、抽出したインデックス値の数を返します。このような関数をマクロで記述したものを示します。
: mymacro macro MNAME, CINST public MNAME align 16 MNAME proc vpbroadcastq zmm1, r8 ; zmm1 = cmpValue vmovdqu32 ymm3, ymmword ptr index ; ymm3 = index mov r8, 8 vpbroadcastd ymm4, r8 ; ymm4 = 8,...,8 xor r8, r8 ; init retrurn value = 0 xor rax, rax ; clear in index loop_f: CINST k1, zmm1, zmmword ptr [rcx+rax*8], _MM_CMPINT_EQ vpcompressd ymmword ptr [rdx+r8*4]{k1}, ymm3 ; store ymm3 to &aIdx[r8] vpaddd ymm3, ymm3, ymm4 ; update index kmovb r10, k1 ; move mask bit popcnt r10, r10 add r8, r10 ; update retrurn value add rax, 8 cmp rax, r9 jb short loop_f ; loop mov eax, r8d ; return value ret MNAME endp endm ;------------------------------------------------------------------- ;.code _TEXT segment mymacro aextIdxSQ, vpcmpq ; QWORD mymacro aextIdxUQ, vpcmpuq ; QWORD Unsigned _TEXT ends ;.data __DATA segment ;.data index dd 0, 1, 2, 3, 4, 5, 6, 7 __DATA ends end
mymacro マクロを定義し、これを使ってaextIdxSQとaextIdxUQ関数を作ります。aextIdxSQ関数は符号付64ビット整数用、aextIdxUQ関数は符号付64ビット整数用の関数です。
分かり易くするため、aextIdxSQが展開されたソースリストを示します。
public aextIdxSQ align 16 aextIdxSQ proc vpbroadcastq zmm1, r8 ; zmm1 = cmpValue vmovdqu32 ymm3, ymmword ptr index ; ymm3 = index mov r8, 8 vpbroadcastd ymm4, r8 ; ymm4 = 8,...,8 xor r8, r8 ; init retrurn value = 0 xor rax, rax ; clear in index loop_f: vpcmpq k1, zmm1, zmmword ptr [rcx+rax*8], _MM_CMPINT_EQ vpcompressd ymmword ptr [rdx+r8*4]{k1}, ymm3 ; store ymm3 to &aIdx[r8] vpaddd ymm3, ymm3, ymm4 ; update index kmovb r10, k1 ; move mask bit popcnt r10, r10 add r8, r10 ; update retrurn value add rax, 8 cmp rax, r9 jb short loop_f ; loop mov eax, r8d ; return value ret aextIdxSQ endp
まず、vpbroadcastq命令でr8 レジスターの値をzmm1レジスターへブロードキャストし初期化します。r8レジスターは指定値を保持しています。これによって、zmm1レジスターの8要素がr8レジスター埋められます。
vmovdqu32命令でymm3レジスターへインデックスの初期値を設定します。vpbroadcastd命令で、ymm4レジスターへymm3レジスターが保持するインデックスの増分値を設定します。本例では、64ビット整数を対象とするため一回で8要素を処理します。これから、インデックスの増分値は8 です。使用しているレジスターから分かるように、値はzmmレジスターで、インデックスはymmレジスターで8要素を処理します。これはインデックス値をint型で管理するためです。
次にr8レジスターとrax レジスターをxor命令でゼロクリアします。r8レジスターは抽出した要素の数を、raxレジスターは一次元配列を参照するためのインデックスを保持します。
前準備が完了したためループへ入ります。配列の処理対象要素と比較値が格納されているzmm1レジスターを比較します。vpcmpq命令で条件に合致する要素のビットがオンになるマスクをk1マスクレジスターへ得ます。このマスクレジスターをvpcompressd命令に指定し、条件に合致するインデックス値を抽出します。
一次元配列は8要素単に処理しますが、抽出するインデックス値がいくつになるか不明です。そこでk1レジスターをr10レジスターへ移動し、popcnt命令でオンになっているビット数を数えます。この値は抽出した要素数を示します。この値をr8レジスターへ加算し、出力配列用のインデックスを管理します。
次に、入力配列用インデックスを保持するraxレジスターへ8を加算し、これを配列の要素数を保持するr9レジスターと比較し、すべての要素を処理したか調べます。処理が終わっていなければ、次の要素の処理へ移ります。全要素の処理が完了したら、r8に格納されている抽出した要素数を、eaxレジスターへ移動し処理を終了します。eaxレジスターは関数の戻り値であるint型の値です。
common.h、共通関数
ヘッダーファイルの一部を示します。簡単な内容なので、説明は省きします。
: // C++ template <typename T> int cextIdx(const T* a, int* vIdx, const T cmpValue, const size_t length) { int rIndex = 0; for (size_t i = 0; i < length; i++) { if (a[i] == cmpValue) { vIdx[rIndex] = i; rIndex++; } } return rIndex; } :
すべての型に対応させるため、テンプレート関数とします。cextIdx関数は、一次元配列から指定値と一致する要素のインデックス値を抽出し、その数を戻します。
このプログラムのビルドし、実行した様子を示します。
実行結果
cmpValue = -10
asm Length = 68
cpp Length = 68