Как я могу использовать расширения SSE (и SSE2, SSE3 и т. д.) при сборке с помощью Visual C++?

Сейчас я работаю над небольшой оптимизацией базовой функции скалярного произведения, используя инструкции SSE в Visual Studio.

Вот мой код: (соглашение о вызове функции cdecl):

float SSEDP4(const vect & vec1, const vect & vec2)
{
    __asm
    {
        // get addresses
        mov ecx, dword ptr[vec1]
        mov edx, dword ptr[vec2]
        // get the first vector
        movups xmm1, xmmword ptr[ecx]
        // get the second vector (must use movups, because data is not assured to be aligned to 16 bytes => TODO align data)
        movups xmm1, xmmword ptr[edx]
        // OP by OP multiply with second vector (by address)
        mulps xmm1, xmm2
        // add everything with horizontal add func (SSE3)
        haddps xmm1, xmm1
        // is one addition enough ?
        // try to extract, we'll see
        pextrd eax, xmm1, 03h
    }
}

vect — это простая структура, содержащая 4 числа с плавающей запятой одинарной точности, не выровненные по 16 байтам (поэтому я использую movups, а не movaps)

vec1 инициализируется с помощью (1.0, 1.2, 1.4, 1.0) и vec2 с (2.0, 1.8, 1.6, 1.0)

Все компилируется хорошо, но при выполнении я получил 0 в обоих регистрах XMM, поэтому в результате во время отладки Visual Studio показывает мне 2 регистра (MMX1 и MMX2, или иногда MMX2 и MMX3), которые являются 64-битными регистрами, но не XMM и все на 0.

Кто-нибудь имеет представление о том, что происходит?

Заранее спасибо :)


person Grégoire    schedule 15.08.2011    source источник
comment
Обратите внимание, что Visual Studio не является компилятором; Visual C++ является фактическим компилятором; Visual Studio — это IDE. (поэтому я отредактировал вопрос)   -  person Billy ONeal    schedule 15.08.2011
comment
Отладка + Windows + Регистры. Щелкните правой кнопкой мыши окно и отметьте SSE2.   -  person Hans Passant    schedule 15.08.2011


Ответы (2)


Есть несколько способов получить инструкции SSE в MSVC++:

  1. Внутренние компоненты компилятора -> http://msdn.microsoft.com/en-us/library/t467de55.aspx
  2. Внешний файл MASM.

Встроенная сборка (как в вашем примере кода) больше не является разумным вариантом, потому что она не будет компилироваться при сборке для не 32-битных систем x86. (Например, создание 64-битного двоичного файла не удастся)

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

person Billy ONeal    schedule 15.08.2011
comment
Я бы повторил комментарии Билли выше и настоятельно рекомендовал бы вам использовать встроенные функции везде, где это возможно. Обратите внимание, что Win8 будет доступна на чипах ARM, поэтому, если вы хотите, чтобы ваш код мог работать на ARM с помощью простой перекомпиляции, избегайте сборки везде, где это возможно. - person Rich Turner; 15.08.2011
comment
Привет, спасибо за ваш быстрый ответ! У меня еще есть небольшой вопрос: можно ли использовать файлы MASM непосредственно в функциях? Или они определяют функцию для вызова из кода? - person Grégoire; 15.08.2011
comment
@Gregoire Вы определяете функцию в файле сборки и вызываете ее из кода. Конечно, вам нужно будет объявить функцию перед ее вызовом. - person Billy ONeal; 16.08.2011
comment
Определенно не гарантируется, что встроенные функции @Richard будут работать на разных процессорах. - person Jasper Bekkers; 16.08.2011
comment
@Jasper: Ну, SSE в целом не работает на разных процессорах. Это не нарушение внутренней структуры, это нарушение периода SSE. По крайней мере, вы сможете делать такие вещи, как сборка для x64, используя встроенные функции. Конечно, это не очень поможет вам для ARM, если только MSVC++ не эмулирует их с последовательной версией того же самого. (Не знаю; не могу проверить, потому что общедоступная версия MSVC++ не может быть собрана для ARM) - person Billy ONeal; 16.08.2011
comment
Мой опыт показывает, что оптимизатор без проблем встраивает функции, содержащие встроенный ассемблер. Разумеется, он ничего не изменяет внутри блока asm. Но он будет встраивать функции, содержащие asm блоков. По крайней мере, это верно для MSVC 2010, не знаю, насколько далеко это зашло. - person Cody Gray; 09.06.2014
comment
@Cody: Оптимизаторы обычно делают гораздо больше, чем просто встраивают вещи. :) - person Billy ONeal; 11.06.2014

Вы скомпилировали и запустили правильно, так что вы, по крайней мере, можете использовать SSE.

Чтобы просмотреть регистры SSE в окне «Регистры», щелкните правой кнопкой мыши окно «Регистры» и выберите «SSE». Это должно позволить вам увидеть регистры XMM.

Вы также можете использовать @xmm<register><component> (например, @xmm00 для просмотра xmm0[0]) в окне просмотра для просмотра отдельных компонентов регистров XMM.

Теперь, что касается вашей реальной проблемы, вы перезаписываете xmm1 на [edx] вместо того, чтобы вставлять это в xmm2.

Кроме того, скалярные значения с плавающей запятой возвращаются в стек x87 в st(0). Вместо того, чтобы пытаться вспомнить, как это сделать, я просто сохраняю результат в переменной стека и позволяю компилятору сделать это за меня:

float SSEDP4(const vect & vec1, const vect & vec2)
{
    float result;
    __asm
    {
        // get addresses
        mov ecx, dword ptr[vec1]
        mov edx, dword ptr[vec2]
        // get the first vector
        movups xmm1, xmmword ptr[ecx]
        // get the second vector (must use movups, because data is not assured to be aligned to 16 bytes => TODO align data)
        movups xmm2, xmmword ptr[edx] // xmm2, not xmm1
        // OP by OP multiply with second vector (by address)
        mulps xmm1, xmm2
        // add everything with horizontal add func (SSE3)
        haddps xmm1, xmm1
        // is one addition enough ?
        // try to extract, we'll see
        pextrd [result], xmm1, 03h
    }

    return result;
}
person MSN    schedule 15.08.2011
comment
Теперь это работает лучше! Мне также удалось сохранить одну инструкцию, выровняв данные по 16 байтам (используя __declspec(align 16) ), а затем объединив вторые movups и mulps (mulps могут использовать адреса только в режиме выравнивания). Однако, выполнив некоторые тесты (в режиме отладки, без оптимизации компилятора), я обнаружил, что выполнение 100 000 000 раз точечного произведения выполняется быстрее с использованием базовых инструкций, чем с использованием набора инструкций SSE. Кто-нибудь знает, что там произошло? - person Grégoire; 21.08.2011