任意の範囲へ飽和させる飽和処理を紹介します。
64ビット整数型
呼び出し側・符号付
以降に、呼び出し側のC++コードを示します。common.hをincludeしていますがSIMDで記述したアセンブリ言語の関数と等価な機能を持つC++言語で記述した共通関数のヘッダです。最下部にソースコードを示します。条件は関数名に含まれます。
#include "..\common.h" // TEMPLATES #define T long long extern "C" void satULQ(const T*, T*, T, T, 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], c[ArrLen], v[ArrLen]; const T lower = -5000, upper = 3000; init(a, ArrLen); a[ArrLen - 1] = lower - 1; a[ArrLen - 2] = upper + 1; c_Sat (a, c, lower, upper, ArrLen); satULQ(a, v, lower, upper, ArrLen); verify(c, v, ArrLen); return 0; }
符号付64ビット整数型の配列をいくつか宣言し、配列へランダムな値を設定します。確実に関数が動作しているか確認するため、入力配列の最後に下限値と上限値を超える値を設定します。そしてアセンブリ言語で記述した関数とC++言語で開発した関数を呼び出し、結果をverify関数で検証します。
呼び出し側・符号なし
符号なしへ対応した呼び出し側のソースリストを示します。型以外は符号付と同じです。
#include "..\common.h" // TEMPLATES #define T unsigned long long extern "C" void satULUQ(const T*, T*, T, T, 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], c[ArrLen], v[ArrLen]; const T lower = 300, upper = 3000; init(a, ArrLen); a[ArrLen - 1] = lower - 1; a[ArrLen - 2] = upper + 1; c_Sat (a, c, lower, upper, ArrLen); satULUQ(a, v, lower, upper, ArrLen); verify(c, v, ArrLen); return 0; }
先のプログラムは符号付64ビット整数型へ対応させたものでした。本プログラムは、符号なし64ビット整数型へ対応させたものです。変更は僅かです。符号なしですので、下限も上限も正の値を指定します。
呼び出され側
64ビット整数型配列の要素を、渡された範囲内に飽和処理します。以降に、マクロを使って開発したライブラリを示します。アセンブラーのコードを示します。符号ごとに関数を記述するのは面倒なのでマクロを使って記述します。
; macro mymacro macro MNAME, MMAXINST, MMININST public MNAME align 16 MNAME proc vpbroadcastq zmm1, r8 ; zmm1 = lower vpbroadcastq zmm2, r9 ; zmm2 = upper mov r8, qword ptr [rsp+40] ; length xor rax, rax ; clear index loop_f: vmovdqu64 zmm0, zmmword ptr [rcx+rax*8] ; load a[] MMAXINST zmm0, zmm0, zmm1 MMININST zmm0, zmm0, zmm2 vmovdqu64 zmmword ptr [rdx+rax*8], zmm0 add rax, 8 cmp rax, r8 jb short loop_f ret MNAME endp endm ;------------------------------------------------------------------- ; code _TEXT segment mymacro satULQ, vpmaxsq, vpminsq mymacro satULUQ, vpmaxuq, vpminuq _TEXT ends end
本関数へ渡されるレジスターとデータの関係を以降の表に示します。
レジスタ | 内容 |
---|---|
rcx | 入力配列の先頭アドレス |
rdx | 出力配列の先頭アドレス |
r8 | 飽和の上限値 |
r9 | 飽和の下限値 |
rsp+40 | 配列の要素数 |
vpbroadcastq命令で、zmm1に下限値、zmm2に上限値をブロードキャストします。次に、r8レジスターへ要素数を設定します。基本的なループの構造は、これまでのプログラムと同様です。vmovdqu64 命令で、符号付、あるいは符号なし64ビット整数型配列から8個の要素を読み込みます。そして、vpmaxsq命令あるいはvpmaxuq命令で下限値へ飽和させます。次に、その結果を使って、vpminsq命令あるいはvpminuq命令で上限値へ飽和させます。これによって入力の値を、下限値と上限値の範囲へ飽和させ、vmovdqu64 命令で8個の要素を書き込みます。
一回の処理で8要素を処理するため、raxレジスターに8を加算し要素数に達するまで上記処理を繰り返します。なお、先に示したvpmaxsq命令あるいはvpmaxuq命令は、マクロの引数MMAXINST で置き換えられ、vpminsq命令あるいはvpminuq命令はMMININST で置き換えられます。
実行結果(signed)
C:\>ml64 /c satULQAsm.asm
C:\>cl /O2 /EHsc satULQ.cpp satULQAsm.obj
C:\>satULQ
Ok!
実行結果(unsigned)
C:\>ml64 /c satULQAsm.asm
C:\>cl /O2 /EHsc satULUQ.cpp satULQAsm.obj
C:\>satULUQ
Ok!
common.h、共通関数
#include <iostream> using namespace std; // initialize values template <typename T> void init(T* a, const size_t length) { for (size_t i = 0; i < length; i++) { a[i] = (T)(rand() - (RAND_MAX / 2)); } } // initialize values template <typename T> void init(T* a, T* b, const size_t length) { for (size_t i = 0; i < length; i++) { a[i] = (T)(rand() - (RAND_MAX / 2)); b[i] = (T)(rand() - (RAND_MAX / 2)); } } // verify values template <typename T> void verify(T* c, T* v, const size_t length) { bool errorFlag = false; for (size_t i = 0; i < length; i++) { if (c[i] != v[i]) { cout << "Error, " << "i = " << i << ", c = " << c[i] << ", v = " << v[i] << endl; errorFlag = true; break; } } if(!errorFlag) cout << "Ok!" << endl; } template <typename T> void c_Sat(const T* a, T* v, const T lower, const T upper, const size_t length) { for (size_t i = 0; i < length; i++) { T r = a[i] < lower ? lower : a[i]; r = r > upper ? upper : r; v[i] = r; } } // print elapsed time void print_elTime(char* prompt, clock_t start, clock_t end) { float elapsed = static_cast<double>(end - start) / CLOCKS_PER_SEC * 1000.0; printf(prompt); printf("%10.3f [ms]\n", elapsed); }
32ビット整数型
呼び出し側は示さず、呼び出され側のみを示します。
呼び出され側
32ビット整数型へ対応したものを紹介します。以降に、ソースコードの一部を示します。
: mymacro macro MNAME, MMAXINST, MMININST : MNAME proc vpbroadcastd zmm1, r8 ; zmm1 = lower vpbroadcastd zmm2, r9 ; zmm2 = upper mov r8, qword ptr [rsp+40] ; length xor rax, rax ; clear index loop_f: vmovdqu32 zmm0, zmmword ptr [rcx+rax*4] ; load a[] MMAXINST zmm0, zmm0, zmm1 MMININST zmm0, zmm0, zmm2 vmovdqu32 zmmword ptr [rdx+rax*4], zmm0 add rax,16 : _TEXT segment mymacro satULD, vpmaxsd, vpminsd mymacro satULUD, vpmaxud, vpminud _TEXT ends end
vpbroadcastd命令で、zmm1に下限値、zmm2に上限値をブロードキャストします。次に、r8レジスターへ要素数を設定します。
vmovdqu32 命令で、符号付、あるいは符号なし32ビット整数型配列から16個の要素を読み込みます。そして、vpmaxsd命令あるいはvpmaxud命令で下限値へ飽和させます。次に、その結果を使って、vpminsd命令あるいはvpminud命令で上限値へ飽和させます。これによって入力の値を、下限値と上限値の範囲へ飽和させ、vmovdqu32命令で16個の要素を書き込みます。
メモリーへのアクセスは [rcx+rax*4] で指定します。rcxに配列の先頭アドレスが格納されています。オフセットは、配列のインデックスを保持しているraxへ4乗じて求めます。この4は要素のバイト長、つまり32ビット整数をバイト長で表したときのバイト長です。
一回の処理で16要素を処理するため、raxレジスターに16を加算し要素数に達するまで上記処理を繰り返します。なお、先に示したvpmaxsd命令あるいはvpmaxud命令は、マクロの引数MMAXINST で置き換えられ、vpmaxsd命令あるいはvpmaxud命令はMMININST で置き換えられます。 符号なしへ対応した呼び出し側の変更は軽微ですので示しません。
64ビット浮動小数点型
呼び出し側
以降に、呼び出し側のC++コードを示します。common.hをincludeしていますがSIMDで記述したアセンブリ言語の関数と等価な機能を持つC++言語で記述した共通関数のヘッダです。最下部にソースコードを示します。条件は関数名に含まれます。
#include "..\common.h" // TEMPLATES #define T double extern "C" void satULPD(const T*, T*, T, T, 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], c[ArrLen], v[ArrLen]; const T lower = -5000, upper = 3000; init(a, ArrLen); a[ArrLen - 1] = lower - 1; a[ArrLen - 2] = upper + 1; c_Sat (a, c, lower, upper, ArrLen); satULPD(a, v, lower, upper, ArrLen); verify(c, v, ArrLen); return 0; }
呼び出され側
呼び出し側のソースリストを示します。
; code _TEXT segment public satULPD align 16 satULPD proc vbroadcastsd zmm1, xmm2 ; zmm1 = lower vbroadcastsd zmm2, xmm3 ; zmm2 = upper mov r8, qword ptr [rsp+40] ; length xor rax, rax ; clear index loop_f: vmovupd zmm0, zmmword ptr [rcx+rax*8] ; load a[] vmaxpd zmm0, zmm0, zmm1 vminpd zmm0, zmm0, zmm2 vmovupd zmmword ptr [rdx+rax*8], zmm0 add rax, 8 cmp rax, r8 jb short loop_f ret satULPD endp _TEXT ends end
本関数へ渡されるレジスターとデータの関係を以降の表に示します。
レジスタ | 内容 |
---|---|
rcx | 入力配列の先頭アドレス |
rdx | 出力配列の先頭アドレス |
r8 | 飽和の上限値 |
r9 | 飽和の下限値 |
rsp+40 | 配列の要素数 |
vbroadcastsd命令で、zmm1に下限値、zmm2に上限値をブロードキャストします。次に、r8レジスターへ要素数を設定します。
vmovupd命令で、64ビット浮動小数点型から8個の要素を読み込みます。そして、vmaxpd命令で下限値へ飽和させます。次に、その結果を使って、vminpd 命令で上限値へ飽和させます。これによって入力の値を、下限値と上限値の範囲へ飽和させ、vmovupd命令で8個の要素を書き込みます。インデックスやメモリーへのアクセスなどの処理は、64ビット整数型と同様です。
このプログラムのビルドと実行の例を示します。
実行結果(signed)
C:\>ml64 /c satULPDAsm.asm
…
C:\>cl /O2 /EHsc satULPD.cpp satULPDAsm.obj
…
C:\>satULPD
Ok!
32ビット浮動小数点型
呼び出し側のソースコードは示しません。
呼び出され側
呼び出し側のソースリストを示します。
; code _TEXT segment public satULPS align 16 satULPS proc vbroadcastss zmm1, xmm2 ; zmm1 = lower vbroadcastss zmm2, xmm3 ; zmm2 = upper mov r8, qword ptr [rsp+40] ; length xor rax, rax ; clear index loop_f: vmovups zmm0, zmmword ptr [rcx+rax*4] ; load a[] vmaxps zmm0, zmm0, zmm1 vminps zmm0, zmm0, zmm2 vmovups zmmword ptr [rdx+rax*4], zmm0 add rax,16 cmp rax, r8 jb short loop_f ret satULPS endp _TEXT ends end
vpbroadcastd命令で、zmm1に下限値、zmm2に上限値をブロードキャストします。次に、r8レジスターへ要素数を設定します。
vmovdqu32 命令で、符号付、あるいは符号なし32ビット整数型配列から16個の要素を読み込みます。そして、vpmaxsd命令あるいはvpmaxud命令で下限値へ飽和させます。次に、その結果を使って、vpminsd命令あるいはvpminud命令で上限値へ飽和させます。これによって入力の値を、下限値と上限値の範囲へ飽和させ、vmovdqu32命令で16個の要素を書き込みます。インデックスやメモリーへのアクセスなどは、32ビット整数型と同様です。
このプログラムのビルドと実行の例を示します。
実行結果(signed)
C:\>ml64 /c satULPSAsm.asm
…
C:\>cl /O2 /EHsc satULPS.cpp satULPSAsm.obj
…
C:\>satULPS
Ok!