一次元配列へスカラーの加算、C++、アセンブラー、SIMD

一次元配列へスカラー値を加算します。C++x86アセンブラー、AVX2、AVX-512の例を示します。

C++で記述

#include <iostream>
#include <iomanip>

using namespace std;


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

//main
int main(void)
{
    int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
    const size_t units = sizeof(a)/sizeof(a[0]);
    int c[units];
    const int f = 2;

    for(int i = 0; i < units; i++)
    {
        c[i] = a[i] + f;
    }

    cout << " f = " << f << endl;
    printData(units, (int*)a);
    printData(units, (int*)c);

    return 0;
}


 実行結果

cl /EHsc addSisd.cpp
C:\>addSisd
f = 2
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18

x86アセンブラーで記述

まず、C++のコードを示します。

#include <iostream>
#include <iomanip>

using namespace std;

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

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

//main
int main(void)
{
    int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
    const size_t units = sizeof(a)/sizeof(a[0]);
    int c[units];
    const int f = 2;

    //for(int i = 0; i < units; i++)
    //{
    //    c[i] = a[i] + f;
    //}
    asmCode(a, f, c);

    cout << " f = " << f << endl;
    printData(units, (int*)a);
    printData(units, (int*)c);

    return 0;
}

「asmCode(a, f, c);」をコメントアウトし、上の行を生かせばC++単独で動作させることがでる。

アセンブリのコードを示します。

_TEXT   segment

        public asmCode
        align  16

;-------------------------------------------------------------------
; rcx: a, rdx: f, r8: c
;
; a: int[]
; b: int
; c: int[]
;
;---
;for(int i = 0; i < units; i++)
;{
;    c[i] = a[i] + f;
;}
;

;-------------------------------------------------------------------
asmCode proc

                    ;__m256i t = _mm256_set1_epi32(f);
        mov     r9,  rcx
        mov     rcx, 16
        mov     edx, 2
loop_f:
        mov     eax, dword ptr[r9]
        add     eax, edx
        mov     dword ptr [r8], eax

        lea     r8, qword ptr[r8+4]
        lea     r9, qword ptr[r9+4]
        loop    loop_f

        ret

asmCode endp

_TEXT   ends
        end

AVX2で記述

まず、C++のコードを示します。

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

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

//main
int main(void)
{
    __m256i a = _mm256_set_epi32(1, 2, 3, 4, 5, 6, 7, 8);
    __m256i c;
    const int f = 2;

    //__m256i t = _mm256_set1_epi32(f);
    //__m256i c = _mm256_add_epi32(a, t);
    asmCode((int*)&a, f, (int*)&c);

    cout << " f = " << f << endl;
    printData((int*)&a);
    printData((int*)&c);

    return 0;
}

「asmCode((int*)&a, f, (int*)&c);」をコメントアウトし、上の行を生かせばC++単独で動作させることがでる。その際はintrinsicが使われる。

アセンブリのコードを示します。

_TEXT   segment

        public asmCode
        align  16

;-------------------------------------------------------------------
; rcx: a, rdx: f, r8: c
;
; a: aligned
; b: int
; c: aligned
;
;---
;__m256i a = _mm256_setr_epi32(1, 2, 3, 4, 5, 6, 7, 8);
;const int f = 2;
;
;__m256i t = _mm256_set1_epi32(f);
;__m256i c = _mm256_add_epi32(a, t);

;-------------------------------------------------------------------
asmCode proc

                    ;__m256i t = _mm256_set1_epi32(f);
        movd        xmm1, edx
        vbroadcastss ymm1, xmm1

                    ;__m256i c = _mm256_add_epi32(a, t);
        vpaddd      ymm0, ymm1, ymmword ptr [rcx]   ; zmm1 = a + t
        vmovaps     ymmword ptr [r8], ymm0          ; store ymm0 to c

        ret

asmCode endp

_TEXT   ends
        end


AVX-512で記述

まず、C++のコードを示します。

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

extern "C"
void asmCode(const int *a, const int f, 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 c;
    const int f = 2;

    //__m512i t = _mm512_set1_epi32(f);
    //__m512i c = _mm512_add_epi32(a, t);
    asmCode((int*)&a, f, (int*)&c);

    cout << " f = " << f << endl;
    printData((int*)&a);
    printData((int*)&c);

    return 0;
}

「asmCode(a, f, c);」をコメントアウトし、上の行を生かせばC++単独で動作させることがでる。その際はintrinsicが使われる。

アセンブリのコードを示します。

_TEXT   segment

        public asmCode
        align  16

;-------------------------------------------------------------------
; rcx: a, rdx: f, r8: c
;
; a: aligned
; b: int
; c: aligned
;
;---
;__m512i a = _mm512_setr_epi32(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
;const int f = 2;
;
;__m512i t = _mm512_set1_epi32(f);
;__m512i c = _mm512_add_epi32(a, t);
;
;-------------------------------------------------------------------
asmCode proc

                    ;__m512i t = _mm512_set1_epi32(f);
        vpbroadcastd zmm1, edx                      ; zmm1 = t

                    ;__m512i c = _mm512_add_epi32(a, t);
        vpaddd      zmm0, zmm1, zmmword ptr [rcx]   ; zmm1 = a + t
        vmovdqa32   zmmword ptr [r8], zmm0          ; store zmm0 to c

        ret

asmCode endp

_TEXT   ends
        end