x86 — Самый быстрый способ умножения двух векторов 32-битных целых чисел в C ++ с помощью SSE

У меня есть два вектора без знака, оба с размером 4

vector<unsigned> v1 = {2, 4, 6, 8}
vector<unsigned> v2 = {1, 10, 11, 13}

Теперь я хочу умножить эти два вектора и получить новый

vector<unsigned> v_result = {2*1, 4*10, 6*11, 8*13}

Какую операцию SSE использовать? Это кроссплатформенный или только
в некоторых указанных платформах?

Добавление:
Если моя цель — добавить не умножение, я могу сделать это очень быстро:

__m128i a = _mm_set_epi32(1,2,3,4);
__m128i b = _mm_set_epi32(1,2,3,4);
__m128i c;
c = _mm_add_epi32(a,b);

3

Решение

Есть _mm_mul_epu32 который только SSE2 и использует инструкцию pmuludq. Поскольку это инструкция SSE2, 99,9% всех процессоров поддерживают ее (я думаю, что самый современный процессор, который не поддерживает AMD Athlon XP).

Он имеет существенный недостаток в том, что он умножает только два целых числа за раз, потому что он возвращает 64-битные результаты, и вы можете поместить только два из них в регистр. Это означает, что вам, вероятно, придется сделать кучу перетасовок, что увеличит стоимость.

2

Другие решения

Использование набора встроенных функций, таких как _mm_set_epi32 для всех элементов неэффективно. Лучше использовать встроенные функции загрузки. Смотрите это обсуждение для получения дополнительной информации об этом Где инструкции SSE превосходят обычные инструкции . Если массивы выровнены по 16 байтов, вы можете использовать либо _mm_load_si128 или же _mm_loadu_si128 (для выровненной памяти они имеют почти одинаковую эффективность) в противном случае используйте _mm_loadu_si128, Но выровненная память намного эффективнее. Для выравнивания памяти рекомендую _mm_malloc а также _mm_freeили C11 aligned_alloc так что вы можете использовать нормальный free,


Чтобы ответить на остальную часть вашего вопроса, давайте предположим, что у вас есть два вектора, загруженные в регистры SSE __m128i a а также __m128i b

Для версии SSE> = SSE4.1 используйте

_mm_mullo_epi32(a, b);

Без SSE4.1:

Этот код скопирован с Agner Fog’s Библиотека векторных классов (и был плагиат первоначального автора этого ответа):

// Vec4i operator * (Vec4i const & a, Vec4i const & b) {
// #ifdef
__m128i a13    = _mm_shuffle_epi32(a, 0xF5);          // (-,a3,-,a1)
__m128i b13    = _mm_shuffle_epi32(b, 0xF5);          // (-,b3,-,b1)
__m128i prod02 = _mm_mul_epu32(a, b);                 // (-,a2*b2,-,a0*b0)
__m128i prod13 = _mm_mul_epu32(a13, b13);             // (-,a3*b3,-,a1*b1)
__m128i prod01 = _mm_unpacklo_epi32(prod02,prod13);   // (-,-,a1*b1,a0*b0)
__m128i prod23 = _mm_unpackhi_epi32(prod02,prod13);   // (-,-,a3*b3,a2*b2)
__m128i prod   = _mm_unpacklo_epi64(prod01,prod23);   // (ab3,ab2,ab1,ab0)
2

Вероятно, _mm_mullo_epi32 — это то, что вам нужно, хотя он предназначен для целых чисел со знаком. Это не должно вызывать проблем, если v1 и v2 настолько малы, что старшие биты этих целых чисел равны 0. Это SSE 4.1. В качестве альтернативы вы можете рассмотреть _mm_mul_epu32.

1

Вы можете (если SSE 4.1 доступен) использовать

__m128i _mm_mullo_epi32 (__m128i a, __m128i b);

умножить упакованные 32-битные целые числа.
В противном случае вам придется перетасовать оба пакета, чтобы использовать _mm_mul_epu32 дважды. Смотрите @ user2088790 ответ для явного кода.

Обратите внимание, что вы также можете использовать _mm_mul_epi32 но это SSE4, так что вы бы предпочли использовать _mm_mullo_epi32 тем не мение.

1

станд :: преобразование применяет данную функцию к диапазону и сохраняет
результат в другом диапазоне

std::vector<unsigned> result;

std::transform( v1.begin()+1, v1.end(), v2.begin()+1, v.begin(),std::multiplies<unsigned>() );
0
По вопросам рекламы [email protected]