Загрузка и добавление SSE

Предположим, у меня есть два вектора, представленные двумя массивами типа double, каждый из которых имеет размер 2. Я хотел бы добавить соответствующие позиции. Итак, предположим векторы i0 и i1, я бы хотел сложить i0[0] + i1[0] и i0[1] + i1[1] вместе.

Поскольку тип double, мне потребуется два регистра. Хитрость заключалась бы в том, чтобы поместить i0[0] и i1[0], а также i0[1] и i1[1] в другой и просто добавить регистр к себе.

Мой вопрос: если я вызову _mm_load_ps(i0[0]), а затем _mm_load_ps(i1[0]), поместит ли это их в младшие и старшие 64-бита отдельно или заменит регистр вторым load? Как мне поместить оба двойника в один и тот же регистр, чтобы я мог вызывать add_ps после?

Спасибо,


person darksky    schedule 13.02.2012    source источник


Ответы (2)


Я думаю, что вы хотите это:

double i0[2];
double i1[2];

__m128d x1 = _mm_load_pd(i0);
__m128d x2 = _mm_load_pd(i1);
__m128d sum = _mm_add_pd(x1, x2);
// do whatever you want to with "sum" now

Когда вы выполняете _mm_load_pd, он помещает первый двойник в младшие 64 бита регистра, а второй - в старшие 64 бита. Таким образом, после вышеуказанных загрузок x1 содержит два значения double i0[0] и i0[1] (и аналогично для x2). Вызов _mm_add_pd вертикально добавляет соответствующие элементы в x1 и x2, поэтому после сложения sum содержит i0[0] + i1[0] в своих младших 64 битах и ​​i0[1] + i1[1] в своих старших 64 битах.

Редактировать: я должен отметить, что нет смысла использовать _mm_load_pd вместо _mm_load_ps. Как видно из названий функций, вариант pd явно загружает два упакованных числа типа double, а вариант ps загружает четыре упакованных числа с плавающей запятой одинарной точности. Поскольку это чисто побитовое перемещение памяти, и оба они используют модуль SSE с плавающей запятой, использование _mm_load_ps для загрузки double данных не накладывает никаких ограничений. И у _mm_load_ps есть преимущество: его кодировка инструкций на один байт короче, чем _mm_load_pd, поэтому он более эффективен с точки зрения кэша инструкций (и, возможно, декодирования инструкций; я не эксперт во всех тонкостях современных процессоров x86). ). Приведенный выше код с использованием _mm_load_ps будет выглядеть так:

double i0[2];
double i1[2];

__m128d x1 = (__m128d) _mm_load_ps((float *) i0);
__m128d x2 = (__m128d) _mm_load_ps((float *) i1);
__m128d sum = _mm_add_pd(x1, x2);
// do whatever you want to with "sum" now

Приведение не подразумевает никакой функции; это просто заставляет компилятор переинтерпретировать содержимое регистра SSE как содержащее двойные числа вместо чисел с плавающей запятой, чтобы его можно было передать в арифметическую функцию двойной точности _mm_add_pd.

person Jason R    schedule 13.02.2012
comment
Вы, безусловно, можете использовать _mm_load_ps, но вы рискуете снизить производительность гипотетического будущего процессора, который разработан таким образом, что существует штраф за обход домена между операциями с плавающей запятой одинарной и двойной точности. Я не знаю планов по такому процессору, но это не значит, что он никогда не будет реализован; вот почему существуют разные операции загрузки. По общему признанию, это отдаленная возможность, но зачем рисковать? - person Stephen Canon; 13.02.2012
comment
Я согласен, что есть риск ухудшения производительности на будущем процессоре. Я бы предложил рассмотреть (т. е. измерить) любой выигрыш в производительности, который можно получить, используя MOVPS вместо MOVPD для конкретного приложения. Если есть польза от его использования сегодня, и нет никаких указаний на вырисовывающуюся архитектуру, которая будет иметь штраф за это, я бы сделал это. Подобные нагрузки можно легко абстрагировать в тандеме, чтобы в будущем можно было автоматически переключаться на другую реализацию. - person Jason R; 13.02.2012
comment
IMO, вам не нужно возиться с _mm_castps_pd в исходном коде встроенных функций: использование более быстрых эквивалентных инструкций - это причина, по которой у нас есть компиляторы в первую очередь. Это упущенная оптимизация для компилятора, когда он выдает movupd или movapd вместо movups/movaps. (Или movsd хранить (не загружать) вместо movlps). Но на практике у нас есть компиляторы, которые не выполняют эти оптимизации. К счастью (?) с AVX это, наконец, уходит; даже инструкции SSE1 застревают с 2-байтовым префиксом VEX, поэтому все инструкции от SSE1 до SSE3 имеют одинаковую длину (за исключением непосредственных или xmm8..15) - person Peter Cordes; 10.08.2019

Префикс _ps – это сокращение от "packed single", означающее, что он используется с плавающей запятой одинарной, а не двойной точности.

Вместо этого вы хотите _mm_load_pd(). Эта функция принимает 16-байтовый выровненный указатель на первый член массива из двух double и загружает их оба. Итак, вы бы использовали это так:

__m128d v0 = _mm_load_pd(i0);
__m128d v1 = _mm_load_pd(i1);

v0 = _mm_add_pd(v0, v1);
person caf    schedule 13.02.2012
comment
_mm_load_ps на самом деле можно использовать со значениями двойной точности (и в этом есть свои преимущества); см. мой ответ. - person Jason R; 13.02.2012