一次元配列の各要素を検査し、条件を満足していたら、その要素を抽出します。ここでは任意の値以上の要素を抽出する、任意の値以下の要素を抽出する、任意範囲の要素を抽出する例を示します。以降に処理概要を図で示します。
符号付64ビット整数型
呼び出し側・符号付
以降に、呼び出し側のC++コードを示します。一次元配列から条件を満足する要素を要素を抽出します。
common.hをincludeしていますがSIMDで記述したアセンブリ言語の関数と等価な機能を持つC++言語で記述した共通関数のヘッダです。最下部にソースコードを示します。条件は関数名に含まれます。
#include "..\common.h" // TEMPLATES #define T long long extern "C" { int extQLow (const T*, T*, const T, size_t); int extQHigh(const T*, T*, const T, size_t); int extQ (const T*, T*, const T, size_t, const 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], c[ArrLen], v[ArrLen]; const T low = -200, high = 5000; cout << endl << "[ v < " << low << "]" << endl; init(a, ArrLen); int cLen = cextractLow(a, c, low, ArrLen); int aLen = extQLow (a, v, low, ArrLen); verify(cLen, aLen, c, v, ArrLen); cout << endl << "[ v > " << high << "]" << endl; cLen = cextractHigh(a, c, high, ArrLen); aLen = extQHigh (a, v, high, ArrLen); verify(cLen, aLen, c, v, ArrLen); cout << endl << "[ " << low << " < v < " << high << "]" << endl; cLen = cextract(a, c, low, ArrLen, high); aLen = extQ (a, v, low, ArrLen, high); verify(cLen, aLen, c, v, ArrLen); return 0; }
簡単な処理ですので説明は省きます。
呼び出され側
本関数へ渡される内容とレジスターの対応を表で示します。
レジスタ | 内容 |
---|---|
rcx | 入力配列 a の先頭アドレス |
rdx | 出力配列 c の先頭アドレス |
r8 | 任意の値、あるいは下限値(範囲指定の時) |
r9 | 入力配列 a の要素数 |
[rsp+40] | オプション、上限値(範囲指定の時) |
引数が可変なため少し面倒です。このライブラリは、一次元配列の1)任意の値以上、あるいは2)任意の値以下の要素を抽出するものと、3)任意の範囲の要素を抽出します。任意の値以上、あるいは任意の値以下の要素を抽出するときは、任意の値は一つだけ必要です。しかし、任意の範囲の要素を抽出するには、二つの値が必要です。このため、上表を各パターンに分けると以下のようになります。
任意の値以上の要素を抽出、任意の値以下の要素を抽出
レジスタ | 内容 |
---|---|
rcx | 入力配列 a の先頭アドレス |
rdx | 出力配列 c の先頭アドレス |
r8 | 任意の値 |
r9 | 入力配列 a の要素数 |
任意範囲の要素を抽出
rcx | 入力配列 a の先頭アドレス |
---|---|
rdx | 出力配列 c の先頭アドレス |
r8 | 下限値 |
r9 | 入力配列 a の要素数 |
[rsp+40] | 上限値 |
64ビット整数型配列の要素を比較し、条件に合う値を取り出すライブラリを、マクロを使って開発したものを示します。本関数は、符号なし/符号付の両方に対応します。かつ、任意の値以上、任意の値以下、そして任意範囲の要素を抽出する6つのライブラリへ展開されます。
: _MM_CMPINT_LT EQU 1 ; - より小さい < _MM_CMPINT_GT EQU 6 ; - より大きい > : mymacro macro MNAME, CINST, CONDITION, CONDITION2 public MNAME align 16 MNAME proc vpbroadcastq zmm1, r8 ; zmm1 = low/high IFDEF CONDITION2 vpbroadcastq zmm2, qword ptr [rsp+40]; zmm2 = high ENDIF xor r8, r8 ; init retrurn value = 0 xor rax, rax ; clear in index loop_f: vmovdqu64 zmm0, zmmword ptr [rcx+rax*8]; load a[] CINST k1, zmm0, zmm1, CONDITION IFDEF CONDITION2 CINST k2, zmm0, zmm2, CONDITION2 kandb k1, k2, k1 ENDIF vpcompressq zmmword ptr [rdx+r8*8]{k1}, zmm0 ; store zmm0 to &c[r8] 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 mov eax, r8d ; return value ret MNAME endp endm ;------------------------------------------------------------------- ; code _TEXT segment ;QWORD mymacro extQLow, vpcmpq, _MM_CMPINT_LT ; < mymacro extQHigh, vpcmpq, _MM_CMPINT_GT ; > mymacro extQ, vpcmpq, _MM_CMPINT_GT, _MM_CMPINT_LT ; <> ;QWORD Unsigned mymacro extUQLow, vpcmpuq, _MM_CMPINT_LT ; < mymacro extUQHigh, vpcmpuq, _MM_CMPINT_GT ; > mymacro extUQ, vpcmpuq, _MM_CMPINT_GT, _MM_CMPINT_LT ; <> _TEXT ends end
mymacro マクロを定義し、これを使ってextQLow、extQHigh、extQ、extUQLow、extUQHigh、extUQ関数を作ります。各関数の機能を表で示します。
関数名 | 機能 |
---|---|
extQLow | 一次元符号付64ビット整数配列から、任意の値以下の要素を抽出する。 |
extQHigh | 一次元符号付64ビット整数配列から、任意の値以上の要素を抽出する。 |
extQ | 一次元符号付64ビット整数配列から、任意範囲の要素を抽出する。 |
extUQLow | 一次元符号なし64ビット整数配列から、任意の値以下の要素を抽出する。 |
extUQHigh | 一次元符号なし64ビット整数配列から、任意の値以上の要素を抽出する。 |
extUQ | 一次元符号なし64ビット整数配列から、任意範囲の要素を抽出する。 |
分かり易くするため、extQLowが展開されたソースリストを示します。
public extQLow align 16 extQLow proc vpbroadcastq zmm1, r8 ; zmm1 = low/high xor r8, r8 ; init retrurn value = 0 xor rax, rax ; clear index loop_f: vmovdqu64 zmm0, zmmword ptr [rcx+rax*8]; load a[] vpcmpq k1, zmm0, zmm1, _MM_CMPINT_LT vpcompressq zmmword ptr [rdx+r8*8]{k1}, zmm0 ; store zmm0 to &c[r8] 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 mov eax, r8d ; return value ret extQLow endp
まず、vpbroadcastq命令でr8 レジスターの値をzmm1レジスターへブロードキャストします。r8 レジスターは下限値を示す任意の値が格納されています。これによって、zmm1レジスターの8要素がr8 レジスターで埋められます。次にr8 レジスターとrax レジスターをxor命令でゼロクリアします。r8 レジスターは抽出した要素の数を、raxレジスターは一次元配列を参照するためのインデックスを保持します。
前準備が完了したためループへ入ります。zmm1レジスターの値と配列の8要素を順次比較します。vpcmpq命令で条件に合致する要素のビットがオンになるマスクをk1マスクレジスターへ得ます。
このマスクレジスターをvpcompressq命令に指定し、条件を満足する要素を抽出します。
一次元配列は8要素単に処理しますが、抽出する要素がいくつになるか不明です。そこでk1レジスターをr10レジスターへ移動し、popcnt命令でオンになっているビット数を数えます。この値は抽出した要素数を示します。この値をr8レジスターへ加算し、出力配列用のインデックスを管理します。
次に、入力配列用インデックスを保持するraxレジスターへ8を加算し、これを配列の要素数を保持するr9レジスターと比較し、すべての要素を処理したか調べます。処理が終わっていなければ、次の要素の処理へ移ります。全要素の処理が完了したら、r8に格納されている抽出した要素数を、eaxレジスターへ移動し処理を終了します。eaxレジスターは関数の戻り値であるint型の値です。
common.h、共通関数
ヘッダーファイルの一部を示します。簡単な内容なので、説明は省きします。
: // C++ template <typename T> int cextractLow(const T* a, T* v, const T cmpValue, const size_t length) { int rIndex = 0; for (size_t i = 0; i < length; i++) { if (a[i] < cmpValue) v[rIndex++] = a[i]; } return rIndex; } // C++ template <typename T> int cextractHigh(const T* a, T* v, const T cmpValue, const size_t length) { int rIndex = 0; for (size_t i = 0; i < length; i++) { if (a[i] > cmpValue) v[rIndex++] = a[i]; } return rIndex; } // C++ template <typename T> int cextract(const T* a, T* v, const T cmpLow, const size_t length, const T cmpHigh) { int rIndex = 0; for (size_t i = 0; i < length; i++) { if (a[i] > cmpLow && a[i] < cmpHigh) v[rIndex++] = a[i]; } return rIndex; } :
すべての型に対応させるため、テンプレート関数とします。cextractLow関数は、一次元配列から任意の値以上の要素を抽出します。cextractHigh関数は、一次元配列から任意の値以下の要素を抽出します。cextract関数は、一次元配列から任意の範囲の要素を抽出します。
このプログラムのビルドし、実行した様子を示します。
実行結果
[ v < -200]
cpp = 1997
asm = 1997
[ v > 5000]
cpp = 1421
asm = 1421
[ -200 < v < 5000]
cpp = 678
asm = 678
C++で処理した結果と、アセンブリ言語で開発したプログラムが同じ結果です。正常に処理されたようです。
念のため、任意範囲の要素を抽出するextQへ展開された関数のソースリストを示します。
public extQ align 16 extQ proc vpbroadcastq zmm1, r8 ; zmm1 = low/high vpbroadcastq zmm2, qword ptr [rsp+40]; zmm2 = high xor r8, r8 ; init retrurn value = 0 xor rax, rax ; clear index loop_f: vmovdqu64 zmm0, zmmword ptr [rcx+rax*8]; load a[] vpcmpq k1, zmm0, zmm1, _MM_CMPINT_GT vpcmpq k2, zmm0, zmm2, _MM_CMPINT_LT kandb k1, k2, k1 vpcompressq zmmword ptr [rdx+r8*8]{k1}, zmm0 ; store zmm0 to &c[r8] 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 mov eax, r8d ; return value ret extQ endp
zmm1レジスターの値と配列の8要素をvpcmpq命令で2回比較し、k1とk2マスクレジスターに結果を求めます。kandb命令で、両方を満足する条件をk1マスクレジスターへ得ます。以降は、先ほどと同様です。