Самый быстрый способ умножить два вектора 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);

5 ответов

Использование набора встроенных функций, таких как _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:

Этот код скопирован из библиотеки векторных классов Агнера Фога (и был плагиат оригинальным автором этого ответа):

// 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)

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

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

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

__m128i _mm_mullo_epi32 (__m128i a, __m128i b);

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

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

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

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

std::vector<unsigned> result;

std::transform( v1.begin()+1, v1.end(), v2.begin()+1, v.begin(),std::multiplies<unsigned>() );
Другие вопросы по тегам