Как добавить все элементы в массив с помощью SSE2?

Предположим, у меня есть очень простой код, например:

double array[SIZE_OF_ARRAY];
double sum = 0.0;

for (int i = 0; i < SIZE_OF_ARRAY; ++i)
{
    sum += array[i];
}

Я в основном хочу делать те же операции, используя SSE2. Как я могу это сделать?


person Peter Lee    schedule 01.10.2012    source источник
comment
Если вам действительно нужно использовать двойную точность, то, вероятно, не стоит беспокоиться, поскольку в наши дни большинство современных процессоров x86 имеют два FPU. Если вы можете перейти к одинарной точности (т.е. с плавающей запятой), возможно, это стоит сделать. Какое улучшение производительности вам нужно?   -  person Paul R    schedule 02.10.2012
comment
Настоятельно рекомендуется использовать суммирование Кахана. Решения, представленные в вопросе и ответе здесь, уязвимы для ошибок.   -  person Cory Nelson    schedule 28.12.2016


Ответы (1)


Вот очень простая реализация SSE3:

#include <emmintrin.h>

__m128d vsum = _mm_set1_pd(0.0);
for (int i = 0; i < n; i += 2)
{
    __m128d v = _mm_load_pd(&a[i]);
    vsum = _mm_add_pd(vsum, v);
}
vsum = _mm_hadd_pd(vsum, vsum);
double sum = _mm_cvtsd_f64(vsum0);

Вы можете развернуть цикл, чтобы повысить производительность, используя несколько аккумуляторов, чтобы скрыть задержку добавления FP (как предлагает @Mysticial). Разверните 3 или 4 раза с несколькими векторами «суммы» для узкого места при нагрузке и пропускной способности с добавлением FP (один или два за такт) вместо задержки с добавлением FP (один за 3 или 4 цикла):

__m128d vsum0 = _mm_setzero_pd();
__m128d vsum1 = _mm_setzero_pd();
for (int i = 0; i < n; i += 4)
{
    __m128d v0 = _mm_load_pd(&a[i]);
    __m128d v1 = _mm_load_pd(&a[i + 2]);
    vsum0 = _mm_add_pd(vsum0, v0);
    vsum1 = _mm_add_pd(vsum1, v1);
}
vsum0 = _mm_add_pd(vsum0, vsum1);    // vertical ops down to one accumulator
vsum0 = _mm_hadd_pd(vsum0, vsum0);   // horizontal add of the single register
double sum = _mm_cvtsd_f64(vsum0);

Обратите внимание, что массив a предполагается выровненным по 16 байтам, а количество элементов n предполагается кратным 2 (или 4, в случае развернутого цикла).

См. также Самый быстрый способ выполнить горизонтальную сумму векторов с плавающей запятой на x86 для альтернативных способов выполнения горизонтальной суммы вне цикла. Поддержка SSE3 не является полностью универсальной (особенно процессоры AMD стали поддерживать ее позже, чем Intel).

Кроме того, _mm_hadd_pd обычно не самый быстрый способ даже на процессорах, которые его поддерживают, поэтому версия только для SSE2 не будет хуже на современных процессорах. Это вне цикла и в любом случае не имеет большого значения.

person Paul R    schedule 01.10.2012
comment
Я думаю, что это может выиграть от развертывания как минимум 3 итераций. (3 отдельные переменные vsum) - person Mysticial; 02.10.2012
comment
Да, возможно. Вы можете позволить компилятору развернуть его или, возможно, сделать лучше вручную. Однако производительность, вероятно, будет ограничена пропускной способностью памяти, если только это не относительно небольшой набор данных, который находится в кеше, поэтому микрооптимизация может не принести большой пользы. - person Paul R; 02.10.2012
comment
Я не думаю, что компилятору разрешено разбивать узлы, поскольку это нарушает ассоциативность. Тем не менее, я не видел, что он будет делать при расслабленной плавающей запятой. Но я никогда не видел, чтобы компилятор слишком агрессивно оптимизировал встроенные функции SSE. - person Mysticial; 02.10.2012
comment
Да, развертывание цикла компилятора, вероятно, будет просто генерировать несколько загрузок и добавлений без введения дополнительных временных объектов, поэтому вы получите некоторые преимущества (например, от двойного выпуска загрузок), но не такие большие, как от интеллектуального ручного развертывания. Сейчас я добавил к ответу 2-кратную развернутую версию. - person Paul R; 02.10.2012
comment
_mm_hadd_pd не является встроенным SSE2, кстати, это SSE3. - person harold; 02.10.2012
comment
@harold: спасибо, что указали на это - я предполагаю, что SSE3 приемлем, поскольку он существует с 2004 года, но если OP необходимо поддерживать действительно старые процессоры, я, вероятно, могу найти обходной путь. Я все равно обновил вопрос, чтобы убедиться, что это ясно. - person Paul R; 02.10.2012
comment
Я убрал это. Я подозреваю, что вы сказали бы что-то более подобное, если бы написали этот ответ сейчас, а не 4 года назад, но, пожалуйста, отредактируйте, если я стал слишком техническим в параграфе с несколькими аккумуляторами. - person Peter Cordes; 28.12.2016
comment
@PeterCordes: спасибо за улучшения, Питер - очень признателен. Двойная точность — это не та область, в которой я обычно работаю, поэтому мой выбор встроенных функций вполне может быть немного любительским. - person Paul R; 28.12.2016