一次元配列同士の加算、アセンブリ言語(AVX512)で記述

一次元配列同士の加算する。呼び出し側をC++で、加算処理をAVX512アセンブリ言語で記述した例を示す。
まず、単純な例を示す。

単純な例、int、zmmレジスター一つ分の例

呼び出し側

呼び出し側のC++コードを示す。common.hをincludeしているが、これはprintDataなど、既に説明済みの共通関数のヘッダである。

#include <immintrin.h>
#include "../../common.h"

extern "C"
void asmCode(const int *a, const int *b, int *c);

//main
int main(void)
{
    __m512i a = _mm512_set_epi32(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
    __m512i b = _mm512_set_epi32(11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26);
    __m512i c;

    asmCode((int*)&a, (int*)&b, (int*)&c);

    printData((int*)&a);
    printData((int*)&b);
    printData((int*)&c);

    return 0;
}


呼び出され側

アセンブラーのコードを示す。このアセンブリ言語で記述したものは、C++イントリンシックの「__m512i c = _mm512_add_epi32(a, b);」に対応する。

_TEXT   segment

        public asmCode
        align  16

;-------------------------------------------------------------------
; rcx: a, rdx: b, r8: c
;
; a: aligned
; b: aligned
; c: aligned
;-------------------------------------------------------------------
asmCode proc

    vmovdqa32   zmm0, dword ptr [rcx]           ; load a to zmm0
    vpaddd      zmm1, zmm0, zmmword ptr [rdx]   ; zmm1 = a * b
    vmovdqa32   zmmword ptr [r8], zmm1          ; store zmm1 to c

    ret

asmCode endp

_TEXT   ends
        end


 実行結果

C:\>ml64 /c addAsm.asm
C:\>cl /O2 /EHsc /arch:AVX512 add.cpp addAsm.obj
C:\>add
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26
12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42


長いint配列同士の加算、unaligned

alignmentの揃っていない、32ビット整数の一次元配列同士の加算をAVX-512で処理する。
ここで示す例は、arques.hatenablog.comの「AVX-512 intrinsicで記述、intの例。」をアセンブリ言語で書き直したものである。

呼び出し側

呼び出し側のC++コードを示す。

#include <immintrin.h>
#include "../../common.h"

extern "C"
void asmCode(const int* a, const int* b, int* c, size_t length);

// main
int main(void)
{
    const int ArrLen = 256;
    const int AlignSize = 64;

    int* a = new int[ArrLen];
    int* b = new int[ArrLen];
    int* c = new int[ArrLen];
    int* r = new int[ArrLen];

    initData(ArrLen, a, b);

    asmCode(a, b, c, ArrLen);


    // by C++
    for (size_t i = 0; i < ArrLen; i++)
    {
        r[i] = a[i] + b[i];
    }

    verifyVVec(ArrLen, c, r);

    delete[] a, b, c, r;

    return 0;
}


呼び出され側

アセンブラーのコードを示す。

_TEXT   segment

        public asmCode
        align  16

;-------------------------------------------------------------------
; rcx   = &a,  unaligned
; rdx   = &b,  unaligned
; r8    = &c,  unaligned
; r9    = ArrLen
;
;---
;const int units = sizeof(__m512i) / sizeof(int);
;for (size_t i = 0; i < ArrLen / units; i++)
;{
;    __m512i ia = _mm512_loadu_epi32(&a[i * units]);
;    __m512i ib = _mm512_loadu_epi32(&b[i * units]);
;    __m512i y = _mm512_add_epi32(ia, ib);
;    _mm512_storeu_epi32(&c[i * units], y);
;}
;
;---
; asmCode(a, b, c, ArrLen);
;
;-------------------------------------------------------------------
asmCode proc

        mov         r11, rcx                    ; r11 = &a
        mov         rcx, r9                     ; rcx = ArrLen
        sar         rcx, 4                      ; rcx /= 16; (16:sizeof(__mm512i)/sizeof(int))
        xor         r9, r9                      ; init. offset

loop_f:
        vmovdqu32   zmm0, zmmword ptr [r11+r9]  ; load a to zmm0
        vmovdqu32   zmm1, zmmword ptr [rdx+r9]  ; load b to zmm1
        vpaddd      zmm1, zmm0, zmm1            ; zmm1 = a + b
        vmovdqu32   zmmword ptr [r8+r9], zmm1   ; store zmm1 to c

        add         r9, 64                      ; next offset

        loop        loop_f

        ret

asmCode endp

_TEXT   ends
        end


 実行結果

C:\>ml64 /c vAdd512iAsm.asm
C:\>cl /O2 /EHsc /arch:AVX512 vAdd512i.cpp vAdd512iAsm.obj
C:\>vAdd512i







長いint配列同士の加算、aligned

alignmentされている 32 ビット整数の一次元配列同士の加算をAVX-512で処理する。
ここで示す例は、arques.hatenablog.comの「AVX-512 intrinsicで記述、aligned intの例。」をアセンブリ言語で書き直したものである。

呼び出し側

呼び出し側のC++コードを示す。

#include <immintrin.h>
#include "../../common.h"

extern "C"
void asmCode(const int* a, const int* b, int* c, size_t length);

// main
int main(void)
{
    const int ArrLen = 256;
    const int AlignSize = 64;

    int* a = (int*)_mm_malloc(sizeof(int) * ArrLen, AlignSize);
    int* b = (int*)_mm_malloc(sizeof(int) * ArrLen, AlignSize);
    int* c = (int*)_mm_malloc(sizeof(int) * ArrLen, AlignSize);
    int* r = new int[ArrLen];

    initData(ArrLen, a, b);

    asmCode(a, b, c, ArrLen);


    // by C++
    for (size_t i = 0; i < ArrLen; i++)
    {
        r[i] = a[i] + b[i];
    }

    verifyVVec(ArrLen, c, r);

    _mm_free(a);
    _mm_free(b);
    _mm_free(c);
    delete[] r;

    return 0;
}


呼び出され側

アセンブラーのコードを示す。

_TEXT   segment

        public asmCode
        align  16

;-------------------------------------------------------------------
; rcx   = &a,  aligned
; rdx   = &b,  aligned
; r8    = &c,  unaligned
; r9    = ArrLen
;
;---
;const int units = sizeof(__m512i) / sizeof(int);
;__m512i* pa = (__m512i*)a;
;__m512i* pb = (__m512i*)b;
;for (size_t i = 0; i < ArrLen / units; i++, pa++, pb++)
;{
;    __m512i y = _mm512_add_epi32(*pa, *pb);
;    _mm512_store_epi32(&c[i * units], y);
;}
;
;---
; asmCode(a, b, c, ArrLen);
;
;-------------------------------------------------------------------
asmCode proc

        mov         r11, rcx                    ; r11 = &a
        mov         rcx, r9                     ; rcx = ArrLen
        sar         rcx, 4                      ; rcx /= 16;
                                                ;   (16:sizeof(__mm512i)/sizeof(int))
        xor         r9, r9                      ; init. offset
loop_f:
        vmovdqa32   zmm0, zmmword ptr [r11+r9]      ; load a to zmm0
        vpaddd      zmm1, zmm0, zmmword ptr [rdx+r9]; zmm1 = a + b
        vmovdqu32   zmmword ptr [r8+r9], zmm1       ; store zmm1 to c

        add         r9, 64                      ; next offset

        loop        loop_f

        ret

asmCode endp

_TEXT   ends
        end


 実行結果

C:\>ml64 /c vAlignAdd512iAsm.asm
C:\>cl /O2 /EHsc /arch:AVX512 vAlignAdd512i.cpp vAlignAdd512iAsm.obj
C:\>vAlignAdd512i


共通関数

#include <iostream>
#include <iomanip>
#include <random>

using namespace std;

// print
template <typename T>
void printData(T a[])
{
    cout.setf(ios::right);
    for (int i = (sizeof(__m512i) / sizeof(float)) - 1; i >= 0; i--)
        cout << fixed << setprecision(0) << setw(3) << a[i] << ",";
    cout << "\b \b" << endl;
}

// verify
template <typename T>
void verifyVVec(const size_t length, const T* c, T* r)
{
    cout.setf(ios::right);
    for (size_t i = 0; i < length; i++)
    {
        if (c[i] != r[i])
        {
            cout << fixed << setprecision(2)
                << "c[" << i << "] =" << setw(10) << c[i] << ", "
                << "r[" << i << "] =" << setw(10) << r[i] << endl;
        }
    }
}

// random
int inline genRandom(int low, int high)
{
    random_device rd;
    default_random_engine eng(rd());
    uniform_int_distribution<int> distr(low, high);

    return distr(eng);
}

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

// init
template <typename T>
void initData(const size_t length, T* a)
{
    for (size_t i = 0; i < length; i++)
    {
        a[i] = (T)genRandom(-100, 100);
    }
}