Какой самый эффективный способ умножить 4 числа с плавающей запятой на 4 числа с плавающей запятой с помощью SSE?

В настоящее время у меня есть следующий код:

float a[4] = { 10, 20, 30, 40 };
float b[4] = { 0.1, 0.1, 0.1, 0.1 };
asm volatile("movups (%0), %%xmm0\n\t"
             "mulps (%1), %%xmm0\n\t"             
             "movups %%xmm0, (%1)"             
             :: "r" (a), "r" (b));

У меня прежде всего несколько вопросов:

(1) если бы я БЫЛ выровнять массивы по 16-байтовым границам, сработало бы это? Поскольку массивы размещены в стеке, правда ли, что их выравнивание практически невозможно?

см. выбранный ответ для этого сообщения: Выровнены ли переменные стека с помощью GCC __attribute__((aligned(x)))?< /а>

(2) Можно ли вообще переработать код, чтобы сделать его более эффективным? Что, если я помещу в регистры оба массива с плавающей запятой, а не только один?

Спасибо


person horseyguy    schedule 04.08.2009    source источник


Ответы (6)


если бы я ДОЛЖЕН выровнять массивы по 16-байтовым границам, это вообще сработало бы? Поскольку массивы размещены в стеке, правда ли, что их выравнивание практически невозможно?

Требуется, чтобы работало выравнивание по стеку. В противном случае встроенные функции не будут работать. Я предполагаю, что сообщение, которое вы цитировали, было связано с непомерным значением, которое он выбрал для значения выравнивания.

to 2:

Нет, разницы в производительности быть не должно. См. этот сайт, чтобы узнать время инструкций для нескольких процессоров.


Как работает выравнивание переменных стека:

push    ebp
mov ebp, esp
and esp, -16                ; fffffff0H
sub esp, 200                ; 000000c8H

и выравнивает начало стека по 16 байтам.

person Christopher    schedule 04.08.2009

Напишите это на C, используйте

gcc -S -mssse3

если у вас довольно свежая версия gcc.

person xcramps    schedule 04.08.2009
comment
какой код C будет компилироваться в эти инструкции sse? у вас есть пример? - person horseyguy; 04.08.2009
comment
float a[4] = {10, 20, 30, 40}; поплавок b[4] = {0,1, 0,1, 0,1, 0,1}; int foo(void) { int i; для (i=0; i ‹ 4; i++) a[i] *= b[i]; } Скомпилируйте, как показано, и проверьте файл .s. - person xcramps; 04.08.2009

(1) если бы я БЫЛ выровнять массивы по 16-байтовым границам, сработало бы это вообще? Поскольку массивы размещены в стеке, правда ли, что их выравнивание практически невозможно?

Нет, довольно просто выровнять указатель стека с помощью and:

and esp, 0xFFFFFFF0 ; aligned on a 16-byte boundary

Но вы должны использовать то, что предоставляет GCC, например 16-байтовый тип или __attribute__ для настройки выравнивания.

person Bastien Léonard    schedule 04.08.2009
comment
спасибо за ваш ответ, не могли бы вы объяснить мне, как вы можете использовать «и» для выравнивания? я не совсем "понимаю" :) - person horseyguy; 05.08.2009
comment
Напомним, что some_bit and 0 = 0 и a/16 = a>>4, если a без знака. Использование and таким образом установит четыре младших значащих бита в ноль, а остальные оставит без изменений. Что произойдет, если вы разделите esp на 16? Он сдвигается вправо на 4, а четыре «потерянных» бита являются остатком. Таким образом, эти четыре бита должны быть равны 0, так что esp делится на 16. На самом деле происходит вычитание не более 15, так что esp % 16 == 0. (Вычитание из esp означает выделение большего количества место в стеке). - person Bastien Léonard; 05.08.2009

Предоставляет ли GCC поддержку типа данных __m128? Если это так, то это ваш лучший план для гарантированного 16-байтового выровненного типа данных. Тем не менее, есть __attribute__((aligned(16))) для выравнивания вещей. Определите свои массивы следующим образом

float a[4] __attribute__((aligned(16))) = { 10, 20, 30, 40 };
float b[4] __attribute__((aligned(16))) = { 0.1, 0.1, 0.1, 0.1 };

а затем вместо этого используйте movaps :)

person Goz    schedule 04.08.2009
comment
Благодарность; но, как указано в этой статье, кажется невозможным выровнять массивы, выделенные в стеке? (в отличие от глобальных массивов, размещенных в .data) - person horseyguy; 04.08.2009
comment
спасибо за исправление, Бастьен :) Банистер... можешь попробовать и посмотреть, что получится? Если это связано с объяснением правильно, то было бы невозможно правильно выровнять такие вещи, как double, но они ДЕЙСТВИТЕЛЬНО выравниваются. - person Goz; 04.08.2009
comment
да, я скоро... У меня такое ощущение, что связанное объяснение неверно, как, кажется, подразумевают все в этом вопросе. всем спасибо! :) - person horseyguy; 04.08.2009

Использование встроенного намного быстрее, особенно с оптимизацией. Я написал простой тест и сравнил обе версии (asm и встроенную)

unsigned long long time1;
__m128 a1,b1;


a1=_mm_set_ps(10, 20,30,40);
b1=_mm_set_ps(0.1, 0.1, 0.1, 0.1);
float a[4] = { 10, 20, 30, 40 };
float b[4] = { 0.1, 0.1, 0.1, 0.1 };

time1=__rdtsc();
a1=_mm_mul_ps(a1,b1);
time1=__rdtsc() - time1 ;
printf("Time: %llu\n",time1);


time1=__rdtsc();
asm volatile("movups (%0), %%xmm0\n\t"
                 "mulps (%1), %%xmm0\n\t"
                 "movups %%xmm0, (%1)"
                 :: "r" (a), "r" (b));
time1=__rdtsc() - time1 ;
printf("Time: %llu\n",time1);

Встроенная версия 50-60 временных меток процессора Версия Asm ~1000 временных меток процесса

Вы можете проверить это на своей машине

person AlekseyM    schedule 23.11.2012

О рефакторинге. Вы можете использовать встроенный. Пример:

#include <emmintrin.h>

int main(void)
{
    __m128 a1,b1;

    a1=_mm_set_ps(10, 20,30,40);
    b1=_mm_set_ps(0.1, 0.1, 0.1, 0.1);

    a1=_mm_mul_ps(a1,b1);

    return 0;
}

С оптимизацией gcc (-O2 , -O3) может работать быстрее, чем asm.

person AlekseyM    schedule 23.11.2012
comment
как вы думаете, насколько быстрее он будет работать? не могли бы вы сравнить? - person toxicate20; 23.11.2012
comment
смотри следующий пост, я тестирую - person AlekseyM; 23.11.2012