配列の値によって処理を変更するプログラム紹介します。
AVX-512 命令のマスクレジスターを使用すると、プレディケーション(分岐の排除)によって、条件判断が必要な処理を効率良く実行できます。
プレディケーションでは、各レーンで命令を実行するかしないか(または演算の結果をレジスターに書き込むか書き込まないか)をマスクレジスターによって制御できます。以降に、逐次処理で条件判断が必要な処理を実行する場合と、ベクトル命令でプレディケーションした例を図とコードで示します。
このようにフローの制御を行う必要があるとします。このような処理をマスクレジスターで制御すると分岐を排除できます。
呼び出し側
32ビット浮動小数点型配列の要素を、渡された値と比較します。条件を満足したら渡された値と加算し、そうでなければ値をクリアします。比較対象の値と設定する値は別々に渡されます。
以降に、呼び出し側のC++コードを示します。common.hをincludeしていますがSIMDで記述したアセンブリ言語の関数と等価な機能を持つC++言語で記述した共通関数のヘッダです。最下部にソースコードを示します。条件は関数名に含まれます。
それぞれ、lt=less than、le= less equal、eq=equal、ne=not equal、ge=grater qual、gt=grater thanです。
#include "..\common.h" // TEMPLATES // asmbler関数名: 処理+条件+型 #define T float extern "C" { void addzltps(T*, const T, const size_t, const T); void addzeqps(T*, const T, const size_t, const T); void addzleps(T*, const T, const size_t, const T); void addzneps(T*, const T, const size_t, const T); void addzgeps(T*, const T, const size_t, const T); void addzgtps(T*, const T, const size_t, const T); } typedef void (*Dfunc)(T*, const T, const size_t, const T); extern "C" Dfunc afunc[] = { addzeqps, addzltps, addzleps, addzneps, addzgeps, addzgtps }; Dfunc cfunc[] = { caddzeq, caddzlt, caddzle, caddzne, caddzge, caddzgt }; // 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], c[ArrLen]; const T cmpValue = 8, value = 12; for (int i = 0; i < sizeof(cfunc) / sizeof(*cfunc);i++) { cout << "---[" << i << "]--- "; init(a, c, ArrLen); cfunc[i](c, cmpValue, ArrLen, value); afunc[i](a, cmpValue, ArrLen, value); verify(a, c, ArrLen); } return 0; }
呼び出され側
アセンブラーのコードを示します。条件ごとに関数を記述するのは面倒なのでマクロを使って記述します。
;------------------------------------------------------------------- _MM_CMPINT_EQ EQU 0 ; - 等しい == _MM_CMPINT_LT EQU 1 ; - より小さい < _MM_CMPINT_LE EQU 2 ; - 以下 <= _MM_CMPINT_NE EQU 4 ; - 等しくない != _MM_CMPINT_GE EQU 5 ; - 以上 >= _MM_CMPINT_GT EQU 6 ; - より大きい > ;------------------------------------------------------------------- ; macro mymacro macro MNAME, CONDITION public MNAME align 16 MNAME proc vbroadcastss zmm1, xmm1 ; zmm1 = cmpValue vbroadcastss zmm2, xmm3 ; zmm2 = value xor rax, rax ; clear index loop_f: vmovups zmm0, zmmword ptr [rcx+rax*4] ; load r[] vcmpps k1, zmm0, zmm1, CONDITION vaddps zmm3{k1}{z}, zmm0, zmm2 ; add value or zero vmovups zmmword ptr [rcx+rax*4], zmm3 ; stote r[] add rax, 16 cmp rax, r8 jb short loop_f ret MNAME endp endm ;------------------------------------------------------------------- ; code _TEXT segment ;QWORD mymacro addzeqps, _MM_CMPINT_EQ ; == mymacro addzleps, _MM_CMPINT_LE ; <= mymacro addzltps, _MM_CMPINT_LT ; < mymacro addzneps, _MM_CMPINT_NE ; != mymacro addzgeps, _MM_CMPINT_GE ; >= mymacro addzgtps, _MM_CMPINT_GT ; > _TEXT ends end
実行結果
C:\>cl /O2 /EHsc addZPS.cpp addZPSAsm.obj
Microsoft(R) C/C++ Optimizing Compiler Version 19.37.32822 for x64
Copyright (C) Microsoft Corporation. All rights reserved.
addZPS.cpp
Microsoft (R) Incremental Linker Version 14.37.32822.0
Copyright (C) Microsoft Corporation. All rights reserved.
/out:addZPS.exe
addZPS.obj
addZPSAsm.obj
C:\>addZPS
---[0]--- Ok!
---[1]--- Ok!
---[2]--- Ok!
---[3]--- Ok!
---[4]--- Ok!
---[5]--- Ok!
C:\>