AVX2 целочисленное умножение 8-битных элементов со знаком, дающее 16-битные результаты со знаком?

У меня есть два вектора __m256i, заполненные 32 8-битными целыми числами. Что-то вроде этого:

    __int8 *a0 = new __int8[32] {2};
    __int8 *a1 = new __int8[32] {3};

    __m256i v0 = _mm256_loadu_si256((__m256i*)a0);
    __m256i v1 = _mm256_loadu_si256((__m256i*)a1);

Как я могу умножить эти векторы, используя что-то вроде _mm256_mul_epi8(v0, v1) (которого не существует) или каким-либо другим способом?

Мне нужны 2 вектора результатов, потому что ширина выходного элемента в два раза больше ширины входного элемента. Или что-то, что работает аналогично _mm_mul_epu32, будет нормально, используя только четные элементы ввода (0, 2, 4 и т. Д.)


person KaraUL    schedule 17.09.2018    source источник
comment
вы можете увидеть это stackoverflow.com/questions/ 8193601 /   -  person user338371    schedule 17.09.2018
comment
_mm256_mul_epi32 существует. Вы имеете в виду _mm256_mul_epi8? Также укажите, какой результат вы хотите: 16 бит? Младшие 8 бит? Старшие 8 бит? Насыщенный 8-битный результат?   -  person Paul R    schedule 17.09.2018
comment
@PaulR, извините, я действительно имею в виду _mm256_mul_epi8, которого не существует. Если возможно, я хочу получить 16-битный результат.   -  person KaraUL    schedule 18.09.2018
comment
Я отредактировал ваш вопрос, чтобы конкретизировать подписи входов / выходов и включить детали из вашего комментария. Пожалуйста, отредактируйте еще раз, если что-то, что я добавил, на самом деле не то, что вам нужно.   -  person Peter Cordes    schedule 18.09.2018
comment
Очевидный способ - распаковать в 16-битные элементы (неудобно для подписанных из-за пересечения полосы движения, но вы всегда можете распаковать и использовать vpmovsx) и использовать _mm256_mullo_epi16. Вы также можете использовать _mm256_maddubs_epi16 с некоторой маскировкой, чтобы получить четные / нечетные элементы, если вы может иметь дело с одним входом без знака, а другой с подписью. (0*anything + a*b = a*b, поэтому у вас есть расширяющееся умножение, для которого требуется всего лишь одна инструкция AND.)   -  person Peter Cordes    schedule 18.09.2018
comment
@PeterCordes, спасибо, _mm256_maddubs_epi16 определенно то, что мне нужно! (В моем случае все входные данные беззнаковые, теперь я понимаю, насколько важна эта деталь)   -  person KaraUL    schedule 18.09.2018
comment
@KaraUL: Прочтите документы внимательно: maddubs принимает один вход со знаком и один вход без знака. Если один из ваших беззнаковых входов имеет ограниченный диапазон, как всегда 0..127, то вы можете использовать его как подписанный вход.   -  person Peter Cordes    schedule 18.09.2018


Ответы (1)


Вы хотите, чтобы результат был разделен на два вектора, поэтому это мое предложение по вашему вопросу. Я старался быть ясным, простым и понятным:

#include <stdio.h>
#include <x86intrin.h>
 void _mm256_print_epi8(__m256i );
 void _mm256_print_epi16(__m256i );
 void _mm256_mul_epi8(__m256i , __m256i , __m256i* , __m256i* );


int main()
{
    char a0[32] = {1, 2, 3, -4, 5, 6, 7, 8, 9, -10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, -24, 25, 26, 27, 28, 29, 30, 31, 32};
    char a1[32] = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, -13, 14, 15, 16, 17, 18, 19, -20, 21, 22, 23, 24, -25, 26, 27, 28, 29, 30, 31, 32, 33};

    __m256i v0 = _mm256_loadu_si256((__m256i*) &a0[0]);
    __m256i v1 = _mm256_loadu_si256((__m256i*) &a1[0]);

    __m256i r0, r1;//for 16 bit results

    _mm256_mul_epi8(v0, v1, &r0, &r1);

    printf("\nv0 = ");_mm256_print_epi8(v0);
    printf("\nv1 = ");_mm256_print_epi8(v1);
    printf("\nr0 = ");_mm256_print_epi16(r0);
    printf("\nr1 = ");_mm256_print_epi16(r1);
    printf("\nfinished\n");


    return 0;
}
//v0 and v1 are 8 bit input vectors. r0 and r1 are 18 bit results of multiplications
 void _mm256_mul_epi8(__m256i v0, __m256i v1, __m256i* r0, __m256i* r1)
{
    __m256i tmp0, tmp1;
    __m128i m128_v0, m128_v1;

    m128_v0 = _mm256_extractf128_si256 (v0, 0);
    m128_v1 = _mm256_extractf128_si256 (v1, 0);

    tmp0= _mm256_cvtepi8_epi16 (m128_v0); //printf("\ntmp0 = ");_mm256_print_epi16(tmp0);
    tmp1= _mm256_cvtepi8_epi16 (m128_v1); //printf("\ntmp1 = ");_mm256_print_epi16(tmp1);


    *r0 =_mm256_mullo_epi16(tmp0, tmp1);

    m128_v0 = _mm256_extractf128_si256 (v0, 1);
    m128_v1 = _mm256_extractf128_si256 (v1, 1);

    tmp0= _mm256_cvtepi8_epi16 (m128_v0); //printf("\ntmp0 = ");_mm256_print_epi16(tmp0);
    tmp1= _mm256_cvtepi8_epi16 (m128_v1); //printf("\ntmp1 = ");_mm256_print_epi16(tmp1);

    *r1 =_mm256_mullo_epi16(tmp0, tmp1);


}
 void _mm256_print_epi8(__m256i vec)
{
    char temp[32];
    _mm256_storeu_si256((__m256i*)&temp[0], vec);
    int i;
    for(i=0; i<32; i++)
        printf(" %3i,", temp[i]);


}

 void _mm256_print_epi16(__m256i vec)
{
    short temp[16];
    _mm256_storeu_si256((__m256i*)&temp[0], vec);
    int i;
    for(i=0; i<16; i++)
        printf(" %3i,", temp[i]);   
}

Результат:

[martin@mrt Stack over flow]$ gcc -O2 -march=native mul_epi8.c -o out
[martin@mrt Stack over flow]$ ./out

v0 =    1,   2,   3,  -4,   5,   6,   7,   8,   9, -10,  11,  12,  13,  14,  15,  16,  17,  18,  19,  20,  21,  22,  23, -24,  25,  26,  27,  28,  29,  30,  31,  32,
v1 =    2,   3,   4,   5,   6,   7,   8,   9,  10,  11,  12, -13,  14,  15,  16,  17,  18,  19, -20,  21,  22,  23,  24, -25,  26,  27,  28,  29,  30,  31,  32,  33,
r0 =    2,   6,  12, -20,  30,  42,  56,  72,  90, -110, 132, -156, 182, 210, 240, 272,
r1 =  306, 342, -380, 420, 462, 506, 552, 600, 650, 702, 756, 812, 870, 930, 992, 1056,
finished
[martin@mrt Stack over flow]$ 

ПРИМЕЧАНИЕ. Я прокомментировал промежуточные результаты tmp0 и tmp1 в рекомендованном коде. Кроме того, как Питер предложил в комментариях и предоставил ссылку на Godbolt, если ваша программа загружается из памяти и вам не нужно умножать элементы в векторах, вы можете использовать этот код:

#include <immintrin.h>

//v0 and v1 are 8 bit input vectors. r0 and r1 are 18 bit results of multiplications
__m256i mul_epi8_to_16(__m128i v0, __m128i v1)
{
    __m256i tmp0 = _mm256_cvtepi8_epi16 (v0); //printf("\ntmp0 = ");_mm256_print_epi16(tmp0);
    __m256i tmp1 = _mm256_cvtepi8_epi16 (v1); //printf("\ntmp1 = ");_mm256_print_epi16(tmp1);

    return _mm256_mullo_epi16(tmp0, tmp1);
}

__m256i mul_epi8_to_16_memsrc(char *__restrict a, char *__restrict b){

    __m128i v0 = _mm_loadu_si128((__m128i*) a);
    __m128i v1 = _mm_loadu_si128((__m128i*) b);
    return mul_epi8_to_16(v0, v1);
}


int main()
{
    char a0[32] = {1, 2, 3, -4, 5, 6, 7, 8, 9, -10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, -24, 25, 26, 27, 28, 29, 30, 31, 32};
    char a1[32] = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, -13, 14, 15, 16, 17, 18, 19, -20, 21, 22, 23, 24, -25, 26, 27, 28, 29, 30, 31, 32, 33};

    __m256i r0 = mul_epi8_to_16_memsrc(a0, a1);

}
person Martin    schedule 22.09.2018
comment
Вероятно, вы захотите использовать 128-битные входные векторы, чтобы можно было скомпилировать vpmovsxbw ymm0, [mem] загрузку, а не узкое место на порте случайного воспроизведения из-за распаковки с _mm256_extractf128_si256. Кроме того, это позволит вашей вспомогательной функции просто возвращать один __m256i. - person Peter Cordes; 23.09.2018
comment
Некоторые отрицательные входные данные были бы хороши для ваших тестовых примеров, чтобы показать, что _mm256_cvtepi8_epi16 правильно расширяет знак, в отличие от распаковки с нулем или использования _mm256_cvtepu8_epi16 - person Peter Cordes; 23.09.2018
comment
@PeterCordes, Привет, Питер, на самом деле я думаю, что использование vpmovsxbw, внутреннее значение которого __m256i _mm256_cvtepi8_epi16 (__m128i a) для элементов, представленных в векторе, может быть не лучше, чем извлечение. Потому что мне нужно писать в кеш и снова читать из него. Я что-то упускаю? - person Martin; 23.09.2018
comment
Компиляторы могут складывать _mm_loadu_si128 в источник памяти для _mm256_cvtepi8_epi16. Нет магазина / перезагрузки. godbolt.org/z/GyX-V7 - это версия вашей функции со 128-битными входами. и обертка, которая встраивает его в результат двух загрузок. OP показывает, что они загружаются из памяти, поэтому в результате некоторых вычислений у них еще нет __m256i векторов. - person Peter Cordes; 23.09.2018