最小値・最大値・インデックス

一次元配列に含まれる最小値か最大値を見つけ、かつその位置を得るプログラムを紹介します。以降に処理の概要図を示します。

64ビット整数型

呼び出し側・符号付

以降に、呼び出し側のC++コードを示します。common.hをincludeしていますがSIMDで記述したアセンブリ言語の関数と等価な機能を持つC++言語で記述した共通関数のヘッダです。最下部にソースコードを示します。条件は関数名に含まれます。

#include "..\common.h"  // TEMPLATES

#define T long long
extern "C"
{
    T aminsq(const T*, const size_t, int*);
    T amaxsq(const T*, const size_t, int*);
}

// 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];
    int   cminIndex, aminIndex, cmaxIndex, amaxIndex;

    init(a, ArrLen);
    T cmin = cMin  (a, ArrLen, &cminIndex);
    T amin = aminsq(a, ArrLen, &aminIndex);

    T cmax = cMax  (a, ArrLen, &cmaxIndex);
    T amax = amaxsq(a, ArrLen, &amaxIndex);

    cout << fixed << setprecision(2);
    cout << "cpp min = " << cmin << ", index = " << cminIndex << endl;
    cout << "asm min = " << amin << ", index = " << aminIndex << endl;
    cout << "cpp max = " << cmax << ", index = " << cmaxIndex << endl;
    cout << "asm max = " << amax << ", index = " << amaxIndex << endl;

    return 0;
}

一次元配列にランダムな値を与え、先ほどの関数を呼び出し、配列内の最小値や最大値を表示します。

呼び出され側

64ビット整数型一次元配列に含まれる要素の、最小値や最大値を探し、そのインデックス値ます。以降に、マクロを使って開発したアセンブラーのコードを示します。最小値や最大値、そして符号ごとに関数を記述するのは面倒なのでマクロを使って記述します。本関数へ渡されるレジスターとデータの関係を以降の表に示します。

レジスタ 内容
rcx 入力配列の先頭アドレス
rdx 配列の要素数
r8 探した値の位置を返すint型のアドレス
mymacro macro           MNAME, MINST, COND

        public          MNAME
        align           16
MNAME   proc
        vmovdqu64       zmm3, zmmword ptr index ; zmm3 = index
        vmovdqa64       zmm5, zmm3              ; zmm5 = index(min/max)
        mov             r10, 8
        vpbroadcastq    zmm2, r10               ; zmm2 = 8,...,8
        vmovdqu64       zmm0, qword ptr [rcx]   ; zmm0 = initial value

        xor             rax, rax                ; clear index
loop_f:
        vmovdqu64       zmm1, zmmword ptr [rcx+rax*8] ; load a[]
        MINST           k1, zmm1, zmm0, COND
        vmovdqa64       zmm0{k1}, zmm1          ; zmm0 = {k1}a[]

        vmovdqa64       zmm5{k1}, zmm3          ; unpate index(min/max)
        vpaddq          zmm3, zmm3, zmm2        ; next index

        add             rax, 8
        cmp             rax, rdx
        jb              short loop_f


                                                ; - reduce min/max, integer -
        vextracti64x4   ymm1, zmm0, 1           ; d0,  d1, d2, d3 | d4, d5, d6, d7
        MINST           k1, ymm1, ymm0, COND
        vmovdqa64       ymm0{k1}, ymm1

        vextracti64x2   xmm1, ymm0, 1
        MINST           k2, xmm1, xmm0, COND
        vmovdqa64       xmm0{k2}, xmm1

        vpsrldq         xmm1, xmm0, 8
        MINST           k3, xmm1, xmm0, COND
        vmovdqa64       xmm0{k3}, xmm1

        vmovq           rax, xmm0               ; rax = return value


                                                ; - reduce index, integer -
        vmovdqa64       zmm0, zmm5              ; load index, restore from zmm5

        vextracti64x4   ymm1, zmm0, 1           ; d0,  d1, d2, d3 | d4, d5, d6, d7
        vmovdqa64       ymm0{k1}, ymm1

        vextracti64x2   xmm1, ymm0, 1
        vmovdqa64       xmm0{k2}, xmm1

        vpsrldq         xmm1, xmm0, 8
        vmovdqa64       xmm0{k3}, xmm1

        vmovd           dword ptr [r8], xmm0    ; return index

        ret
MNAME   endp

        endm

;-------------------------------------------------------------------
;.code
_TEXT   segment

mymacro aminsq, vpcmpq,  _MM_CMPINT_LT  ; <
mymacro amaxsq, vpcmpq,  _MM_CMPINT_GT  ; >

mymacro aminuq, vpcmpuq, _MM_CMPINT_LT  ; <
mymacro amaxuq, vpcmpuq, _MM_CMPINT_GT  ; >

_TEXT   ends

;.data
__DATA  segment         ;.data
index   dq      0, 1, 2, 3, 4, 5, 6, 7
__DATA  ends
        end
end

mymacro マクロを定義し、これを使ってaminsq、aminuq、amaxsq、amaxuq関数を作ります。各関数の機能を表で示します。

関数名 機能
aminsq 一次元符号付64ビット整数配列から最小値とその位置を取り出す。
aminuq 一次元符号なし64ビット整数配列から最小値とその位置を取り出す。
amaxsq 一次元符号付64ビット整数配列から最大値とその位置を取り出す。
amaxuq 一次元符号なし64ビット整数配列から最大値とその位置を取り出す。

コード量は多くありませんが、若干面倒な処理を行っていますので、関数の内容を先頭から説明します。本プログラムはマクロで記述しているため、命令や条件が置き換えられます。このため一般的に説明すると面倒になるため、符号付64ビット整数型配列から最小値を求める場合を解説します。
vmovdqu64命令でzmm3レジスターへインデックスの初期値を設定します。zmm5レジスターへ同じ値を設定しますが、このレジスターは探した最小値のインデックスを保持します。vpbroadcastq命令で、zmm2レジスターへzmm3レジスターが保持するインデックスの増分値を設定します。本例では、64ビット整数を対象とするため一回で8要素を処理します。これから、インデックスの増分値は8 です。
ループ処理へ入る前に、vmovdqu64命令で、zmm0レジスターに配列の先頭の要素を読み込みます。これは、最小値を探すための初期値です。次にraxレジスターをクリアします。このraxレジスターは配列を参照するインデックスを保持します。最初の値は読み込み済みですので、次の処理対象のインデックスを設定するのが良いのでしょうが、ここでは単純にクリアします。
前準備が終わったのでループ処理に入ります。vmovdqu64命令で、処理対象の要素をzmm1レジスターへ読み込みます。読み込むアドレスは、先頭アドレスを保持しているrcxレジスターへ、インデックスを保持するraxレジスターへ8(64ビット整数型のバイト長)を乗じた値を加算することによって求めます。
次に、MINST を指定していますが、符号付64ビット整数型配列から最小値を求める場合はvpcmpq命令へ、そして第三引数の条件は_MM_CMPINT_LTへ置き換えられます。これでk1マスクレジスターへ更新する要素のビットが立ったマスク値が格納されます。
vmovdqa64命令へk1マスクレジスターと、読み込んだzmm1レジスターを与えることによって、最小値を保持するzmm0レジスターを更新します。これを繰り返すことによって、zmm0レジスターに8要素単位の最小値が求まります。先のプログラムは、vpminsq命令を使用しましたが、本プログラムは値だけでなくインデックスも返す必要もあるためvpcmpq命令を使用し、マスクレジスターへ値を設定します。
vmovdqa64命令へk1マスクレジスターと、読み込んだ配列のインデックスを保持するzmm3レジスターを与えることによって最小値のインデックスを保持するzmm5レジスターを更新します。これを繰り返すことによって、zmm3レジスターは最小値を保持するzmm0レジスターに対応したインデックス値を保持します。
読み込んだ配列のインデックスを保持するzmm3レジスターの値はループのたびに更新します。以降に図で示します。

インデックス値はint型ですが、データの値とインデックスの値を対応させるため、zmm3レジスターやzmm2レジスターの値は64ビット整数値とします。
これを全要素が終わるまで繰り返します。ループを繰り返す必要があるか判断するため、raxレジスターへ8(64ビット整数型のバイト長)を加算します。全要素の完了検査は、lengthが格納されているrdxレジスターと配列のインデックスを保持するraxレジスターを比較して判断します。全部の処理が終わっていなければループの先頭に戻り、そうでなければループを抜けます。

全要素の処理が完了するとzmm0レジスターへ8要素の最小値が格納され、zmm5レジスターへzmm0レジスターに格納された要素に対応するインデックス値が格納されます。この二つの8要素をreduce処理し、それぞれの最終の値を求めます。以降に、reduceのコードを示します。実際のコードはマクロで書かれていますが、ここでは符号付64ビット整数型の最小値を求めるように展開されたコードを示します。

vextracti64x4   ymm1, zmm0, 1           ; d0,  d1, d2, d3 | d4, d5, d6, d7
vpcmpq          k1, ymm1, ymm0, _MM_CMPINT_LT
vmovdqa64       ymm0{k1}, ymm1

vextracti64x2   xmm1, ymm0, 1
vpcmpq          k2, xmm1, xmm0, _MM_CMPINT_LT
vmovdqa64       xmm0{k2}, xmm1

vpsrldq         xmm1, xmm0, 8
vpcmpq          k3, xmm1, xmm0, _MM_CMPINT_LT
vmovdqa64       xmm0{k3}, xmm1

vmovq           rax, xmm0               ; rax = return value

上記のコードを順次図を示しながら説明します。基本的にレジスターを半分に畳みながら、順次結果を得る方法を採用します。

まず、zmmレジスターに格納されている8個の要素を4個の要素にreduceしymmレジスターへ格納します。zmm0レジスターに格納されている上位4要素をymm1レジスターへ抽出します。下位4要素はzmm0レジスターの下位であるymm0レジスターへ残されます。この二つのレジスターをvpcmpq 命令へ与え、条件で与えた_MM_CMPINT_LT から小さな値を持つ要素に対応したビットがセットされ、k1マスクレジスターへ設定されます。
次に、このマスクレジスターをvmovdqa64命令に与え、ymm1レジスターの要素がymm0レジスターの要素より小さければ、ymm1レジスターからymm0レジスターへ移動します。これによって、8個の要素が4個の要素へreduceされます。このマスクレジスターは、インデックスを求めるときも利用しますので壊さないようにします。
8要素を4要素にreduceできましたので、今度は4要素を2要素にreduceします。

基本的な処理方法は同じです。使用するレジスターがymmレジスターからxmmレジスターへ変わり、マスクレジスターはk2を利用します。
4要素を2要素にreduceできましたので、最後に2要素を1要素にreduceします。

基本的な処理方法は同じです。使用するレジスターがxmmレジスターですが、上位は無視します。マスクレジスターはk3を利用します。


値のreduceができましたので、インデックス値をreduceします。全要素の処理が完了すると、zmm0レジスターに格納された要素に対応するインデックス値が、zmm5レジスターへ格納されます。zmm5レジスターの8要素をreduce処理し、最終のインデックス値を求めます。ただし、インデックス値に相関や規則性はないため、先の処理で作成したマスクレジスターk1、k2、k3を利用します。以降に、reduceのコードを示します。

vmovdqa64       zmm0, zmm5              ; load index, restore from zmm5

vextracti64x4   ymm1, zmm0, 1           ; d0,  d1, d2, d3 | d4, d5, d6, d7
vmovdqa64       ymm0{k1}, ymm1

vextracti64x2   xmm1, ymm0, 1
vmovdqa64       xmm0{k2}, xmm1

vpsrldq         xmm1, xmm0, 8
vmovdqa64       xmm0{k3}, xmm1

vmovd           dword ptr [r8], xmm0    ; return index

先の最小値をreduceする処理から比較処理を抜き、作成済みのマスクレジスターk1、k2、k3を利用してインデックス値をreduceします。インデックス値が格納されているzmm5レジスターをzmm0レジスターへ代入し、以降は先の処理を簡略化して処理します。要素の値を処理したときに得た、マスクレジスターk1、k2、k3を利用するため比較は不要です。

common.h、共通関数

ヘッダーファイルの一部を示します。簡単な内容なので、説明は省きします。

    :
// min
template <typename T>
T cMin(const T* a, const size_t length, int* index)
{
    T r = a[0];
    for (size_t i = 0; i < length; i++)
    {
        if (r > a[i])
        {
            r = a[i];
            *index = i;
        }
    }
    return r;
}

// max
template <typename T>
T cMax(const T* a, const size_t length, int* index)
{
    T r = a[0];
    for (size_t i = 0; i < length; i++)
    {
        if (r < a[i])
        {
            r = a[i];
            *index = i;
        }
    }
    return r;
}
    :

すべての型に対応させるため、テンプレート関数とします。cMin関数は一次元配列から最小値とインデックス値を探し、cMax 関数は最大値とインデックス値を探します。

このプログラムの実行例を示します。

 実行結果(signed)

C:\> minmaxWidxQ
cpp min = -16362, index = 1880
asm min = -16362, index = 1880
cpp max = 16382, index = 2367
asm max = 16382, index = 2367

C++言語で記述した関数と、アセンブリ言語で開発した関数が同じ値を返しています。

reduceのまとめ

zmmレジスターに格納されている64ビット整数をreduceする方法は解説しましたが、32ビット整数、16ビット整数、8ビット整数、64ビット浮動小数点、そして32ビット浮動小数点については説明していません。ここでは、それらについて説明しましょう。

32ビット整数をreduce

以降に、32ビット整数をreduceするコードと、それを図で示します。実際のコードはマクロで書かれていますが、ここでは符号付32ビット整数型の最小値を求めるように展開されたコードを示します。

vextracti32x8   ymm1, zmm0, 1                   ; d0,  d1, ...  d7 | d8,  d9, ... d15
vpcmpd          k1, ymm1, ymm0, _MM_CMPINT_LT
vmovdqa32       ymm0{k1}, ymm1

vextracti128    xmm1, ymm0, 1
vpcmpd          k2, xmm1, xmm0, _MM_CMPINT_LT
vmovdqa32       xmm0{k2}, xmm1

pshufd          xmm1, xmm0, 14                  ; 14 = _MM_SHUFFLE(0, 0, 3, 2), 0x0E = 14
vpcmpd          k3, xmm1, xmm0, _MM_CMPINT_LT
vmovdqa32       xmm0{k3}, xmm1

pshufd          xmm1, xmm0, 1                   ;  1 = _MM_SHUFFLE(0, 0, 0, 1), 0x01 = 1
vpcmpd          k4, xmm1, xmm0, _MM_CMPINT_LT
vmovdqa32       xmm0{k4}, xmm1

vmovd           eax, xmm0                       ; eax = return value

zmm0レジスターに格納されている16個の符号付32ビット整数から最小値を取り出す方法を解説します。先のプログラムは値だけを取り出しますが、本プログラムはインデックスを取り出すためvpcmpd命令とマスクレジスターを利用します。上記のコードを順次図を示しながら説明します。基本的にレジスターを半分に畳みながら、順次結果を得る方法を採用します。
まず、zmmレジスターに格納されている16個の要素を8個の要素にreduceしymmレジスターへ格納します。

zmm0レジスターに格納されている上位8要素をymm1レジスターへ抽出します。下位8要素はzmm0レジスターの下位であるymm0レジスターへ残されます。この二つのレジスターをvpcmpq 命令へ与え、条件で与えた_MM_CMPINT_LT から小さな値を持つ要素に対応したビットがセットされ、k1マスクレジスターへ設定されます。
次に、このマスクレジスターをvmovdqa32命令に与え、ymm1レジスターの要素がymm0レジスターの要素より小さければ、ymm1レジスターからymm0レジスターへ移動します。これによって、16個の要素が8個の要素へreduceされます。このマスクレジスターは、インデックスを求めるときも利用しますので壊さないようにします。
16要素を8要素にreduceできましたので、今度は8要素を4要素にreduceします。

ymm0レジスターに格納されている上位4要素をxmm1レジスターへ抽出します。下位4要素はymm0レジスターの下位であるxmm0レジスターへ残されます。この二つのレジスターをvpcmpq 命令へ与え、条件で与えた_MM_CMPINT_LT から小さな値を持つ要素に対応したビットがセットされ、k2マスクレジスターへ設定されます。
次に、このマスクレジスターをvmovdqa32命令に与え、xmm1レジスターの要素がxmm0レジスターの要素より小さければ、xmm1レジスターからxmm0レジスターへ移動します。これによって、8個の要素が4個の要素へreduceされます。このマスクレジスターは、インデックスを求めるときも利用しますので壊さないようにします。
8要素を4要素にreduceできましたので、今度は4要素を2要素にreduceします。

xmm0レジスターに格納されている上位2要素をxmm1レジスターへ抽出します。下位2要素はxmm0レジスターの下位へ残されます。この二つのレジスターをvpcmpq 命令へ与え、条件で与えた_MM_CMPINT_LT から小さな値を持つ要素に対応したビットがセットされ、k3マスクレジスターへ設定されます。
次に、このマスクレジスターをvmovdqa32命令に与え、xmm1レジスターの要素がxmm0レジスターの要素より小さければ、xmm1レジスターからxmm0レジスターへ移動します。これによって、4個の要素が2個の要素へreduceされます。処理自体はxmmレジスターの4要素を対象としますが、上位の2要素は意味がありません。このマスクレジスターは、インデックスを求めるときも利用しますので壊さないようにします。
4要素を2要素にreduceできましたので、今度は2要素を1要素にreduceします。

xmm0レジスターに格納されている下位2要素の内の上位要素をxmm1レジスターの最下位へ移動します。下位1要素はxmm0レジスターの最下位へ残されます。この二つのレジスターをvpcmpq 命令へ与え、条件で与えた_MM_CMPINT_LT から小さな値を持つ要素に対応したビットがセットされ、k4マスクレジスターへ設定されます。
次に、このマスクレジスターをvmovdqa32命令に与え、xmm1レジスターの要素がxmm0レジスターの要素より小さければ、xmm1レジスターからxmm0レジスターへ移動します。これによって、2個の要素が1個の要素へreduceされます。処理自体はxmmレジスターの4要素を対象としますが、上位の3要素は意味がありません。このマスクレジスターは、インデックスを求めるときも利用しますので壊さないようにします。
最後に、xmm0レジスターの最下位要素をeaxレジスターへ移動します。これは関数の戻り値として使用されます。

値のreduceができましたので、インデックス値をreduceします。全要素の処理が完了すると、zmm0レジスターに格納された要素に対応するインデックス値が、zmm5レジスターへ格納されます。zmm5レジスターの16要素をreduce処理し、最終のインデックス値を求めます。インデックス値に相関や規則性はないため、先の処理で作成したマスクレジスターk1、k2、k3、k4を利用します。以降に、reduceのコードを示します。

vmovdqa32       zmm0, zmm5              ; load index, restore from zmm5

vextracti32x8   ymm1, zmm0, 1           ; d0,  d1, ...  d7 | d8,  d9, ... d15
vmovdqa32       ymm0{k1}, ymm1

vextracti128    xmm1, ymm0, 1
vmovdqa32       xmm0{k2}, xmm1

pshufd          xmm1, xmm0, 14          ; 14 = _MM_SHUFFLE(0, 0, 3, 2), 0x0E = 14
vmovdqa32       xmm0{k3}, xmm1

pshufd          xmm1, xmm0, 1           ;  1 = _MM_SHUFFLE(0, 0, 0, 1), 0x01 = 1
vmovdqa32       xmm0{k4}, xmm1

vmovd           dword ptr [r8], xmm0    ; return index

先の最小値をreduceする処理から比較処理を抜き、作成済みのマスクレジスターk1、k2、k3、k4を利用してインデックス値をreduceします。インデックス値が格納されているzmm5レジスターをzmm0レジスターへ代入し、以降は先の処理を簡略化して処理します。要素の値を処理したときに得た、マスクレジスターk1、k2、k3、k4を利用するため比較は不要です。

64ビット浮動小数点をreduce

以降に、64ビット浮動小数点をreduceするコードを示します。64ビット整数型とほとんど同じ方法で処理しますが、使用する命令が整数型用から浮動小数点型へ変わります。実際のコードはマクロで書かれていますが、ここでは64ビット浮動小数点型の最小値を求めるように展開されたコードを示します。

                                                ; - reduce min/max, double -
        vextractf64x4   ymm1, zmm0, 1           ; d0,  d1, d2, d3 | d4, d5, d6, d7
        MINST           k1, ymm1, ymm0
        vmovapd         ymm0{k1}, ymm1

        vextractf64x2   xmm1, ymm0, 1
        vcmpltpd        k2, xmm1, xmm0
        vmovapd         xmm0{k2}, xmm1

        vpermilpd       xmm1, xmm0, 1           ;  1 = _MM_SHUFFLE(0, 0, 0, 1)
        vcmpltpd        k3, xmm1, xmm0
        vmovapd         xmm0{k3}, xmm1

        vmovapd         xmm3, xmm0              ; save it


                                                ; - reduce index, integer -
        vmovdqa64       zmm0, zmm5              ; load index, restore from zmm5

        vextracti64x4   ymm1, zmm0, 1           ; d0,  d1, d2, d3 | d4, d5, d6, d7
        vmovdqa64       ymm0{k1}, ymm1

        vextracti64x2   xmm1, ymm0, 1
        vmovdqa64       xmm0{k2}, xmm1

        vpsrldq         xmm1, xmm0, 8
        vmovdqa64       xmm0{k3}, xmm1

        vmovd           dword ptr [r8], xmm0    ; return index

最初に説明した[64ビット整数型配列]のreduceとほとんど同じです。扱うデータが64ビット整数から、64ビット浮動小数点へ変わりますが、畳み込む要素数は同じです。データ型が異なるため、型に合った命令に変更するだけで、使用する命令もほとんど同じです。

32ビット浮動小数点をreduce

以降に、32ビット浮動小数点をreduceするコードを示します。実際のコードはマクロで書かれていますが、ここでは32ビット浮動小数点の最小値を求めるように展開されたコードを示します。まず、値のreduceのソースコードを示します。

        vextractf32x8   ymm1, zmm0, 1           ; d0,  d1, ...  d7 | d8,  d9, ... d15
        vcmpltps        k1, ymm1, ymm0
        vmovaps         ymm0{k1}, ymm1

        vextractf128    xmm1, ymm0, 1
        vcmpltps        k2, xmm1, xmm0
        vmovaps         xmm0{k2}, xmm1

        vpermilps       xmm1, xmm0, 14          ; 14 = _MM_SHUFFLE(0, 0, 3, 2)
        vcmpltps        k3, xmm1, xmm0
        vmovaps         xmm0{k3}, xmm1

        vpermilps       xmm1, xmm0, 1           ;  1 = _MM_SHUFFLE(0, 0, 0, 1)
        vcmpltps        k4, xmm1, xmm0
        vmovaps         xmm0{k4}, xmm1

        vmovaps         xmm3, xmm0              ; save it

[32ビット整数をreduce]とほとんど同じです。扱うデータが32ビット整数から、32ビット浮動小数点へ変わりますが、畳み込む要素数は同じです。データ型が異なるため、型に合った命令に変更するだけで、使用する命令もほとんど同じです。

zmm0レジスターに格納されている16個の32ビット浮動小数点から最小値を取り出します。基本的にレジスターを半分に畳みながら、順次結果を得る方法を採用します。

zmm0レジスターに格納されている上位8要素をymm1レジスターへ抽出します。下位8要素はzmm0レジスターの下位であるymm0レジスターへ残されます。この二つのレジスターをvcmpltps命令へ与え小さな値を持つ要素に対応したビットがセットされ、k1マスクレジスターへ設定されます。
次に、このマスクレジスターをvmovaps命令に与え、ymm1レジスターの要素がymm0レジスターの要素より小さければ、ymm1レジスターからymm0レジスターへ移動します。これによって、16個の要素が8個の要素へreduceされます。このマスクレジスターは、インデックスを求めるときも利用しますので壊さないようにします。以降も、要素数が変わるだけで同様にreduceします。
今度は8要素を4要素にreduceします。

ymm0レジスターに格納されている上位4要素をxmm1レジスターへ抽出します。
次に、4要素を2要素にreduceします。

xmm0レジスターに格納されている下位2要素をxmm1レジスターの最下位要素へ抽出します。
今度は2要素を1要素にreduceします。

これでxmm1レジスターの最下位要素へ目的の値が抽出されます。処理中に作成したマスクレジスターk1、k2、k3、k4はインデックスのreduceで使用しますので壊してはなりません。

値のreduceができましたので、インデックス値をreduceします。全要素の処理が完了すると、zmm0レジスターに格納された要素に対応するインデックス値が、zmm5レジスターへ格納されます。zmm5レジスターの16要素をreduce処理し、最終のインデックス値を求めます。インデックス値に相関や規則性はないため、先の処理で作成したマスクレジスターk1、k2、k3、k4を利用します。以降に、reduceのコードを示します。

        vmovdqa32       zmm0, zmm5              ; load index, restore from zmm5

        vextracti32x8   ymm1, zmm0, 1           ; d0,  d1, ...  d7 | d8,  d9, ... d15
        vmovdqa32       ymm0{k1}, ymm1

        vextracti128    xmm1, ymm0, 1
        vmovdqa32       xmm0{k2}, xmm1

        pshufd          xmm1, xmm0, 14          ; 14 = _MM_SHUFFLE(0, 0, 3, 2), 0x0E = 14
        vmovdqa32       xmm0{k3}, xmm1

        pshufd          xmm1, xmm0, 1           ;  1 = _MM_SHUFFLE(0, 0, 0, 1), 0x01 = 1
        vmovdqa32       xmm0{k4}, xmm1

        vmovd           dword ptr [r8], xmm0    ; return index

        vmovaps         xmm0, xmm3              ; xmm0 = return value

上記のコードを図で示します。

既に解説した方法なので説明は省きます。