一次元配列同士の演算をAVX512アセンブリ言語で記述・byte

一次元配列同士の演算(byte)をAVX512アセンブリ言語で記述します。呼び出し側をC++で、演算処理をAVX512アセンブリ言語で記述します。

一次元配列同士の演算(byte)

呼び出し側

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

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

// asmbler関数名: 処理+型
#define T char
extern "C"
{
    void addb(const T*, const T*, T*, const size_t);
    void subb(const T*, const T*, T*, const size_t);
    //void mulb(const T*, const T*, T*, const size_t);
}
typedef void (*Dfunc)(const T*, const T*, T*, const size_t);

extern "C"
Dfunc afunc[] = { addb, subb };
Dfunc cfunc[] = { cadd, csub };


// 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], b[ArrLen], c[ArrLen], v[ArrLen];

    init(a, b, ArrLen);
    for (int i = 0; i < sizeof(cfunc) / sizeof(*cfunc);i++)
    {
        cout << "---[" << i << "]---  ";
        cfunc[i](a, b, c, ArrLen);
        afunc[i](a, b, v, ArrLen);
        verify(c, v, ArrLen);
    }
    return 0;
}


呼び出され側

アセンブラーのコードを示します。演算ごとに関数を記述するのは面倒なのでマクロを使って記述します。

mymacro macro       MNAME, MINST

        public      MNAME
        align       16
MNAME   proc
        mov         r10, rcx        ; r10 = a[]
                                    ; rdx = b[]
                                    ; r8  = c[]
        mov         rcx, r9         ; rcx = length
        sar         rcx, 6          ; length/=64; (64bytes=512bit)
        xor         r9, r9          ; clear index
loop_f:
        vmovdqu8    zmm0, zmmword ptr [r10+r9]      ; load a[]
        MINST       zmm0, zmm0, zmmword ptr [rdx+r9]; c[] = a[] <op> b[]
        vmovdqu8    zmmword ptr [r8+r9], zmm0       ; stote c[]

        lea         r9, qword ptr[r9+64]            ; next offset
        loop        loop_f

        ret
MNAME   endp

        endm

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

mymacro addb,  vpaddb
mymacro subb,  vpsubb

_TEXT   ends
        end


 実行結果

C:\>ml64 /c arithBAsm.asm
C:\>cl /O2 /EHsc arithB.cpp arithBAsm.obj
C:\>arithB
---[0]--- Ok!
---[1]--- Ok!

common.h、共通関数

#include <iostream>

using namespace std;

// initialize
template <typename T>
void init(T* a, T* b, const size_t length)
{
    for (size_t i = 0; i < length; i++)
    {
        a[i] = b[i] = (T)(rand() - (RAND_MAX / 2));
    }
}

// verify
template <typename T>
void verify(const T* a, const T*b, const size_t length)
{
    bool errorFlag = false;
    for (size_t i = 0; i < length; i++)
    {
        if (a[i] != b[i])
        {
            cout << "Error, " << "i = " << i << ", a = "
                        << a[i] << ", b = " << b[i] << endl;
            errorFlag = true;
            break;
        }
    }
    if(errorFlag == false)
        cout << "Ok!" << endl;
}

// add
template <typename T>
void cadd(const T* a, const T* b, T* c, const size_t length)
{
    for (size_t i = 0; i < length; i++)
    {
        c[i] = a[i] + b[i];
    }
}

// sub
template <typename T>
void csub(const T* a, const T* b, T* c, const size_t length)
{
    for (size_t i = 0; i < length; i++)
    {
        c[i] = a[i] - b[i];
    }
}

// mul
template <typename T>
void cmul(const T* a, const T* b, T* c, const size_t length)
{
    for (size_t i = 0; i < length; i++)
    {
        c[i] = a[i] * b[i];
    }
}

// div
template <typename T>
void cdiv(const T* a, const T* b, T* c, const size_t length)
{
    for (size_t i = 0; i < length; i++)
    {
        c[i] = a[i] / b[i];
    }
}