Помощь с умножением Assembly / SSE - PullRequest
2 голосов
/ 03 июня 2010

Я пытался выяснить, как добиться некоторого улучшения в моем коде в очень важной строке:

float x = a*b;
float y = c*d;
float z = e*f;
float w = g*h;

все a, b, c ... являются поплавками.

Я решил изучить использование SSE, но не могу найти никакого улучшения, на самом деле оно оказывается вдвое медленнее. Мой код SSE:

Vector4 abcd, efgh, result;
abcd = [float a, float b, float c, float d];
efgh = [float e, float f, float g, float h];
_asm {
movups xmm1, abcd
movups xmm2, efgh
mulps xmm1, xmm2
movups result, xmm1
}

Я также пытался использовать стандартную встроенную сборку, но, похоже, я не могу упаковать регистр с четырьмя числами с плавающей запятой, как я могу с SSE.

Буду признателен за любые комментарии или помощь, мне в основном нужно понять, почему мои вычисления с использованием SSE медленнее, чем последовательный код C ++?

Я компилирую в Visual Studio 2005, на Windows XP, используя Pentium 4 с HT, если это предоставляет какую-либо дополнительную информацию для помощи.

Заранее спасибо!

Ответы [ 5 ]

3 голосов
/ 03 июня 2010

вы используете невыровненные инструкции, которые очень медленные. Возможно, вы захотите попытаться правильно выровнять ваши данные, 16-байтовую границу и использовать movaps. Лучшая альтернатива - использовать встроенные функции, а не ассемблирование, потому что тогда компилятор может свободно распоряжаться инструкциями, как это кажется необходимым.

3 голосов
/ 03 июня 2010

Как вы узнали, просто замена пары инструкций на SSE не будет работать, потому что вам нужно перетасовать данные в памяти, чтобы правильно загрузить регистры SSE, и это перемещает данные в памяти ( бит, который создает массивы) убьет вашу производительность, так как память очень медленная (кроме жесткого диска, память в наши дни неизменно является узким местом).

Кроме того, невозможно переместить данные между SSE и FPU / ALU без использования записи в ОЗУ с последующим чтением. Современные микросхемы IA32 хорошо справляются с этим конкретным шаблоном (пишите, а затем читайте), но все равно лишают законной силы некоторый кэш, который будет иметь эффект стука.

Чтобы получить максимальную отдачу от SSE, вам нужно взглянуть на весь алгоритм и данные, которые использует алгоритм. Значения a, b, c и d и e, f, g и h должны постоянно находиться в этих массивах, чтобы не было смещения данных в памяти перед загрузкой регистров SSE. Это не просто и может потребовать много доработки вашего кода и данных (вам может потребоваться хранить данные по-разному на диске).

Также стоит указать, что SSE - только 32-битная (или 64-битная, если вы используете double), тогда как FPU - 80-битная (независимо от числа с плавающей запятой или двойной), поэтому вы получите немного другие результаты при использовании SSE по сравнению с использованием FPU. , Только вы знаете, будет ли это проблемой.

1 голос
/ 18 апреля 2012

Это старая ветка, но я заметил ошибку в вашем примере. Если вы хотите выполнить это:

float x = a*b;
float y = c*d;
float z = e*f;
float w = g*h;

Тогда код должен быть таким:

Vector4 aceg, bdfh, result;  // xyzw
abcd = [float a, float c, float e, float g];
efgh = [float b, float d, float f, float h];
_asm {
movups xmm1, abcd
movups xmm2, efgh
mulps xmm1, xmm2
movups result, xmm1
}

И чтобы увеличить скорость, я бы посоветовал вам не использовать отдельный регистр для "результата".

Для начала, не все алгоритмы выиграют от переписывания в SSE. Алгоритмы, управляемые данными (например, алгоритмы, управляемые поисковыми таблицами), плохо переводятся в SSE, потому что много времени теряется при упаковке и распаковке данных в векторы для работы SSE.

Надеюсь, это все еще помогает.

1 голос
/ 03 июня 2010

Вы можете включить использование SSE и SSE2 в опциях программы в более новых версиях VS и, возможно, в 2005 году. Компилировать с использованием экспресс-версии?

Кроме того, ваш код в SSE, вероятно, медленнее, потому что когда вы компилируете последовательный C ++, компилятор умный и очень хорошо делает его очень быстро - например, автоматически помещая их в нужные регистры в нужное время. Если операции выполняются последовательно, компилятор может, например, уменьшить влияние кэширования и разбиения на страницы. Однако встроенный ассемблер в лучшем случае может быть плохо оптимизирован, и его следует избегать, когда это возможно.

Кроме того, вам необходимо выполнить ОГРОМНОЕ количество работ для SSE / 2, чтобы получить заметную выгоду.

0 голосов
/ 18 мая 2013

Во-первых, когда вы выровняли что-то 128 бит (16 байт), вы должны использовать MOVAPS, поскольку это может быть намного быстрее. Компилятор обычно должен давать вам 16-байтовое выравнивание, даже в 32-битных системах.

Ваши строки C / C ++ не делают то же самое, что ваш код sse.

Четыре числа с плавающей запятой в одном регистре xmm умножаются на четыре числа с плавающей запятой в другом регистре. Даю вам:

float x = a*e;
float y = b*f;
float z = c*g;
float w = d*h;

В sse1 вы должны использовать SHUFPS, чтобы переупорядочить числа в обоих регистрах перед умножением.

Также для обработки данных, которые больше, чем кеш процессора, вы можете использовать временные хранилища (MOVNTPS), чтобы уменьшить загрязнение кеша. Обратите внимание, что не временные магазины намного медленнее в других случаях.

...