2013-06-23 1 views
3

У меня есть два неподписанных векторов, и с размером 4быстрый способ перемножения двух векторов 32-битных целых чисел в C++, с SSE

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); 
+0

Даже если компилятор * может * сделать вывод о размере, выравнивании и т. Д., Чтобы удовлетворить векторизацию, я сомневаюсь, что он будет использовать SSE здесь из-за нагрузки/затраты на хранение. –

+0

Вы знаете о '_mm_mul_ps'? – rwols

+0

@rwols: 'mulps' делает однократное умножение, OP хочет беззнаковое целочисленное умножение. – Skizz

ответ

0

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

std::vector<unsigned> result; 

std::transform(v1.begin()+1, v1.end(), v2.begin()+1, v.begin(),std::multiplies<unsigned>()); 
1

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

__m128i _mm_mullo_epi32 (__m128i a, __m128i b); 

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

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

+0

Правильно, я спрашиваю о платформах для этого _mm_mul_epi32. Доступен ли он везде или только в нескольких местах? – WhatABeautifulWorld

+0

См. [Wikipedia/SSE4] (http://en.wikipedia.org/wiki/SSE4) для информации о том, какие архитектуры он будет присутствовать. AMD имеет это с K10 и Intel с Core 2 дня. – Pixelchemist

+0

ОП, похоже, запрашивает 32-битные результаты, а не расширение/полное умножение (32x32-> 64). Поскольку SSE4.1 также добавляет '_mm_mullo_epi32' (' pmulld'), который дает четыре 32-битных результата, это неправильный ответ, а ответ @ user2088790 - правильный ответ. SSE4.1 '_mm_mul_epi32' - это подписанная версия SSE2' _mm_mul_epu32'. –

1

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

+1

Подписанность не имеет значения для низкого слова умножения. Он не должен был быть документирован как подписанное умножение - это не так, это игнорирующее знак умножение. Это глупо, как документирование 'add' как« подписанное дополнение ». Конечно, они совершили ту же ошибку с «imul». – harold

+0

@harold: Согласен. Хорошая точка зрения. Таблица 2.1 в Intel [справочное руководство по SSE4] (http://software.intel.com/sites/default/files/m/9/4/2/d/5/17971-intel_20sse4_20programming_20reference.pdf) довольно запутанна меня. – wim

2

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

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

2

Использование множества встроенных функций, таких как _mm_set_epi32 для всех элементов, неэффективно. Лучше использовать функции загрузки. См. Это обсуждение для получения более подробной информации об этом Where does the SSE instructions outperform normal instructions. Если массивы выровнены по 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 Туман-х Vector Class Library (и была списана первоначальным автором этого ответа):

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