У меня есть два вектора без знака, оба с размером 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);
Есть _mm_mul_epu32 который только SSE2 и использует инструкцию pmuludq. Поскольку это инструкция SSE2, 99,9% всех процессоров поддерживают ее (я думаю, что самый современный процессор, который не поддерживает AMD Athlon XP).
Он имеет существенный недостаток в том, что он умножает только два целых числа за раз, потому что он возвращает 64-битные результаты, и вы можете поместить только два из них в регистр. Это означает, что вам, вероятно, придется сделать кучу перетасовок, что увеличит стоимость.
Использование набора встроенных функций, таких как _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)
Вероятно, _mm_mullo_epi32 — это то, что вам нужно, хотя он предназначен для целых чисел со знаком. Это не должно вызывать проблем, если v1 и v2 настолько малы, что старшие биты этих целых чисел равны 0. Это SSE 4.1. В качестве альтернативы вы можете рассмотреть _mm_mul_epu32.
Вы можете (если SSE 4.1 доступен) использовать
__m128i _mm_mullo_epi32 (__m128i a, __m128i b);
умножить упакованные 32-битные целые числа.
В противном случае вам придется перетасовать оба пакета, чтобы использовать _mm_mul_epu32
дважды. Смотрите @ user2088790 ответ для явного кода.
Обратите внимание, что вы также можете использовать _mm_mul_epi32
но это SSE4, так что вы бы предпочли использовать _mm_mullo_epi32
тем не мение.
станд :: преобразование применяет данную функцию к диапазону и сохраняет
результат в другом диапазоне
std::vector<unsigned> result;
std::transform( v1.begin()+1, v1.end(), v2.begin()+1, v.begin(),std::multiplies<unsigned>() );