Оператор SSE + = для векторов

У меня есть два массива типа double и я хочу выполнить vecA += vecB. Пока что я делаю vecA = vecA + vecB и, насколько мне известно, например, Запись целых чисел i = i + 5 выполняется медленнее, чем i += 5. Поэтому мне интересно, есть ли какая-то функция SSE, которая просто operator+= на __m128d. Я искал и ничего не нашел. Мое приложение тратит около 60% времени на эту vecA = vecA + vecB операцию, поэтому любой прирост производительности будет заметен.

Все массивы в приведенных ниже фрагментах кода выровнены по 16 байт, а len всегда четный.

Исходный код просто

inline void addToDoubleVectorSSE(
         const double * what, const double * toWhat, double * dest, const unsigned int len)
{
   __m128d * _what      = (__m128d*)what;
   __m128d * _toWhat    = (__m128d*)toWhat;

   for ( register unsigned int i = 0; i < len; i+= 2 )
   {
       *_toWhat = _mm_add_pd( *_what, *_toWhat );
       _what++;
       _toWhat++;
   }
}

После прочтения http://fastcpp.blogspot.cz/2011/04/how-to-process-stl-vector-using-sse.html, где автор увеличивает производительность, не записывая сразу в то, что он только что прочитал, я пробовал

__m128d * _what         = (__m128d*)what;
__m128d * _toWhat       = (__m128d*)toWhat;
__m128d * _toWhatBase   = (__m128d*)toWhat;

__m128d _dest1;
__m128d _dest2;

for ( register unsigned int i = 0; i < len; i+= 4 )
{
    _toWhatBase = _toWhat;
    _dest1      = _mm_add_pd( *_what++, *_toWhat++ );
    _dest2      = _mm_add_pd( *_what++, *_toWhat++ );

    *_toWhatBase++ = _dest1;
    *_toWhatBase++ = _dest2;
}

но по скорости никаких улучшений не происходит. Итак, есть ли operator+= для __m128d? Или есть другой способ, которым я могу использовать оператор + = для массивов двойников? Целевой платформой всегда будет Windows (XP и 7) на процессорах Intel i7 с использованием MSVC.


person Daniel Bencik    schedule 27.02.2013    source источник
comment
Почему вы говорите, что i = i + 5 медленнее, чем i += 5?   -  person Carl Norum    schedule 28.02.2013
comment
Я не думаю, что это связано с языком C. если вам нужно некоторое ускорение, хорошей идеей будет проверить код сборки, сгенерированный из ваших источников ...   -  person V-X    schedule 28.02.2013


Ответы (2)


Насколько мне известно, эквивалента += не существует, потому что арифметические операции SSE обычно выполняются из регистров в регистр или из памяти в регистр, но не из регистра в память.

Однако вы можете улучшить свою производительность, воспользовавшись советом из сообщения в блоге, на которое вы указали ссылку. Причина, по которой этот трюк не сработал для вас, заключается в том, что вы не устранили зависимость между двумя инструкциями: побочные эффекты приращения ++ в _what++ и _toWhat++ препятствуют одновременному запуску второй пары операций. Измените свой цикл следующим образом, чтобы добиться улучшения:

for ( register unsigned int i = 0; i < len; i+= 4, _what += 2, _toWhat += 2, _toWhatBase+=2 )
{
    _toWhatBase = _toWhat;
    _dest1      = _mm_add_pd( *_what, *_toWhat );
    _dest2      = _mm_add_pd( *(_what+1), *(_toWhat+1));

    *_toWhatBase = _dest1;
    *(_toWhatBase+1) = _dest2;
}

После изменения операция на _dest2 становится независимой от операции на _dest1

По моим оценкам настенных часов, после этой простой модификации я получил улучшение примерно на 28%.

person Sergey Kalinichenko    schedule 27.02.2013
comment
Спасибо. Я пробовал (скопируйте и вставил ваш код), но время выполнения уменьшилось с 36,14 до 36,13 с. Может ли улучшение быть зависимым от компилятора? Или есть переключатель компилятора, который позволяет выполнять операции над _dest1 и _dest2 параллельно? - person Daniel Bencik; 28.02.2013
comment
@DanBencik Я не знаю, зависит ли это от компилятора или от процессора. Вот мой тестовый код на pastebin. Я использую VS2010. Когда я запускаю его как есть, выводится счетчик часов 2968; когда я закомментирую строки 34..42 и раскомментирую строки 44..52, чтобы вернуться к вашему коду, я получаю 4170 часов. - person Sergey Kalinichenko; 28.02.2013
comment
Когда я запускаю ваш тест, я получаю результаты, аналогичные вашим, поэтому я понятия не имею, почему во всем приложении не проявляются преимущества вашей реализации. Я посмотрю вечером, когда у меня появится профайлер, и доложу. Спасибо! - person Daniel Bencik; 28.02.2013
comment
Я получил некоторые результаты. Мой код также доступен на pastebin, спасибо за сайт. Единственное изменение в моем коде заключается в том, что я не всегда суммирую одни и те же два массива, но я меняю массивы на суммирование. Я распределяю 1000 массивов в a, b, c и их сумма c [i] = a [i] + b [i]. Теперь разница между FAST_SSE и SSE составляет 16281 частоту против 16437 часов. Отвечает ли ›передача памяти - кеша за безразличную скорость? - person Daniel Bencik; 28.02.2013
comment
@DanBencik Я вижу те же результаты с вашим кодом: FAST_SSE мало что меняет. Я думаю, причина в том, что, поскольку вы постоянно добавляете разные числа, скорость кэширования памяти становится критическим узким местом. В этом случае векторизация не сильно поможет: на самом деле, SSE поверх простого += не должен иметь большого значения, если вас замедляет память. - person Sergey Kalinichenko; 28.02.2013
comment
Могу ли я измерить, вызваны ли этим результаты? Например, будет ли это отображаться в большем количестве промахов кеша? Большое спасибо за обсуждение! - person Daniel Bencik; 28.02.2013
comment
@DanBencik Да, я ожидаю, что при увеличении размера массивов и использовании большего количества различных массивов произойдет большое количество промахов кеша (как в модифицированном тесте производительности). - person Sergey Kalinichenko; 28.02.2013

Вы делаете ненужную работу, современные компиляторы автоматически генерируют такой код. Эта функция называется «авто-векторизация». MSVC также поддерживает его в VS2012. Я не мог понять ваш код, поэтому переписал его так:

inline void addToDoubleVectorSSE(
         const double * what, double * toWhat, const unsigned int len)
{
    for (unsigned ix = 0; ix < len; ++ix) 
        toWhat[ix] += what[ix];
}

Что произвело этот машинный код:

00A3102E  xor         eax,eax  
00A31030  movupd      xmm0,xmmword ptr [esp+eax+358h]  
00A31039  movupd      xmm1,xmmword ptr [esp+eax+38h]  
00A3103F  add         eax,10h  
00A31042  addpd       xmm1,xmm0                          // <=== Look!!
00A31046  movupd      xmmword ptr [esp+eax+348h],xmm1  
00A3104F  cmp         eax,320h  
00A31054  jb          wmain+30h (0A31030h) 

Очевидно, вам следует отдать предпочтение этому решению, учитывая, насколько чище выглядит код. При необходимости обновите версию VS.

person Hans Passant    schedule 27.02.2013
comment
Ханс, спасибо за этот момент. Разница во времени выполнения составляет 39,47 с (без SSE) по сравнению с 36,14 с (SSE), так что это не так уж важно, как я думал. - person Daniel Bencik; 28.02.2013