В чем разница между логическими встроенными функциями SSE?

Есть ли разница между логическими встроенными функциями SSE для разных типов? Например, если мы возьмем операцию ИЛИ, есть три встроенных функции: _mm_or_ps, _mm_or_pd и _mm_or_si128, все из которых делают одно и то же: вычисляют побитовое ИЛИ своих операндов. Мои вопросы:

  1. Есть ли разница между использованием того или иного встроенного (с соответствующим приведением типов). Не будет ли каких-либо скрытых затрат, таких как более длительное исполнение в какой-то конкретной ситуации?

  2. Эти встроенные функции соответствуют трем различным инструкциям x86 (por, orps, orpd). Есть ли у кого-нибудь идеи, почему Intel тратит драгоценное пространство кода операции на несколько инструкций, которые делают то же самое?


person Community    schedule 10.05.2010    source источник
comment
(предыдущий ответ удален из-за того, что был глупо ошибочным - моя вина в том, что я слишком привык к VMX)   -  person Crashworks    schedule 10.05.2010


Ответы (3)


Я думаю, что все три фактически одинаковы, то есть 128-битные побитовые операции. Причина существования различных форм, вероятно, историческая, но я не уверен. Я предполагаю, что возможно, что в версиях с плавающей запятой может быть какое-то дополнительное поведение, например когда есть NaN, но это чистая догадка. Для обычных входов инструкции кажутся взаимозаменяемыми, например

#include <stdio.h>
#include <emmintrin.h>
#include <pmmintrin.h>
#include <xmmintrin.h>

int main(void)
{
    __m128i a = _mm_set1_epi32(1);
    __m128i b = _mm_set1_epi32(2);
    __m128i c = _mm_or_si128(a, b);

    __m128 x = _mm_set1_ps(1.25f);
    __m128 y = _mm_set1_ps(1.5f);
    __m128 z = _mm_or_ps(x, y);
        
    printf("a = %vld, b = %vld, c = %vld\n", a, b, c);
    printf("x = %vf, y = %vf, z = %vf\n", x, y, z);

    c = (__m128i)_mm_or_ps((__m128)a, (__m128)b);
    z = (__m128)_mm_or_si128((__m128i)x, (__m128i)y);

    printf("a = %vld, b = %vld, c = %vld\n", a, b, c);
    printf("x = %vf, y = %vf, z = %vf\n", x, y, z);
    
    return 0;
}

Терминал:

$ gcc -Wall -msse3 por.c -o por
$ ./por

a = 1 1 1 1, b = 2 2 2 2, c = 3 3 3 3
x = 1.250000 1.250000 1.250000 1.250000, y = 1.500000 1.500000 1.500000 1.500000, z = 1.750000 1.750000 1.750000 1.750000
a = 1 1 1 1, b = 2 2 2 2, c = 3 3 3 3
x = 1.250000 1.250000 1.250000 1.250000, y = 1.500000 1.500000 1.500000 1.500000, z = 1.750000 1.750000 1.750000 1.750000
person Paul R    schedule 10.05.2010
comment
ORPD / ORPS предназначены только для SSE, а не для MMX. - person Potatoswatter; 10.05.2010
comment
Но Intel представила orps и позже orpd и то, и другое после por. И физическая основа SSE никогда сильно не менялась. - person Potatoswatter; 11.05.2010
comment
Физическая основа SSE сильно изменилась много, особенно со времен Woodcrest, когда он, наконец, стал полноценным 128-битным устройством. Однако это, вероятно, не имеет значения - похоже, что я могу ошибаться относительно того, почему существуют отдельные побитовые инструкции ИЛИ - я думал, что в старые времена это было устаревшей вещью, связанной с переключением контекста между операциями SSE с целыми числами и с плавающей запятой, но, возможно, нет. - person Paul R; 11.05.2010
comment
re: предположение в первом абзаце: все версии поразрядных логических операций в точности идентичны, за исключением размера инструкции и производительности. Создание NaN с помощью побитовых операций FP ничего особенного не сделает. IDK, если производительность (пересылка данных с доменом FP ​​по сравнению с доменом vector-int) или удобство для программистов / ортогональность набора insn (отсутствие необходимости использовать int ops для данных FP) была более важным мотивирующим фактором. Я должен написать ответ, так как я читал кое-что, о чем никто не упоминал ... - person Peter Cordes; 05.07.2015
comment
Их лучше не менять в случайном порядке из-за задержки обхода данных, которая инструкции на самом деле стоят дополнительный цикл, это очень зависит от инструкций / микроархитектуры, т.е. на Nehalem есть задержка обхода 1c на shufps / shufd, но на haswell ее нет. Но как правило, если существует такая же эффективная инструкция для того же типа данных, что и окружающие, используйте ее. - person Noah; 16.04.2021

  1. Есть ли разница между использованием того или иного встроенного (с соответствующим приведением типов). Не будет ли каких-либо скрытых затрат, таких как более длительное исполнение в какой-то конкретной ситуации?

Да, могут быть причины выбора одного из вариантов по сравнению с другим.

1: Иногда возникает один или два дополнительных цикла задержки (задержка пересылки), если выход целочисленного исполнительного блока необходимо направить на вход исполнительного блока FP, или наоборот. Для перемещения 128 байт данных в любое из множества возможных мест назначения требуется МНОГО проводов, поэтому разработчикам ЦП приходится идти на компромиссы, например, иметь только прямой путь от каждого выхода FP к каждому входу FP, а не ко ВСЕМ возможным входам.

См. этот ответ или Документ по микроархитектуре Агнера Фога для обхода задержек. Найдите задержку обхода данных на Nehalem в документе Агнера; в нем есть несколько хороших практических примеров и обсуждение. У него есть соответствующий раздел для каждого проанализированного им микроархитектуры.

Однако задержки при передаче данных между разными доменами или разными типами регистров меньше на Sandy Bridge и Ivy Bridge, чем на Nehalem, и часто равны нулю. - Документ по микроархитектуре Агнера Фога

Помните, что задержка не имеет значения, если она не находится на критическом пути вашего кода (кроме случаев, когда в Haswell / Skylake он заражает более позднее использование созданного значения, спустя много времени после фактического обхода: /). Использование pshufd вместо movaps + shufps может быть преимуществом, если вашим узким местом является пропускная способность uop, а не задержка критического пути.

2: версия ...ps занимает на 1 байт кода меньше, чем две другие для устаревшего кодирования SSE. (Не AVX). Это по-разному выровняет следующие инструкции, что может иметь значение для декодеров и / или строк кэша uop. Как правило, меньший размер лучше для лучшей плотности кода в I-кэше и извлечения кода из ОЗУ и его упаковки в кеш-память uop.

3: Последние процессоры Intel могут запускать только версии FP на порту 5.

  • Merom (Core2) и Penryn: orps могут работать на p0 / p1 / p5, но только в целочисленном домене. Предположительно все 3 версии декодируются в один и тот же uop. Таким образом, происходит задержка переадресации между доменами. (Процессоры AMD также делают это: побитовые инструкции FP выполняются в домене ivec.)

  • Nehalem / Sandybridge / IvB / Haswell / Broadwell: por может работать на p0 / p1 / p5, но orps может работать только на порту 5. p5 также необходим для перемешивания, но блоки FMA, FP add и FP mul находятся на портах 0/1.

  • Skylake: por и orps оба имеют пропускную способность 3 на цикл. В руководстве Intel по оптимизации есть некоторая информация об обходе задержек пересылки: в / из инструкций FP это зависит от того, на каком порту работает uop. (Обычно все еще порт 5, потому что модули FP add / mul / fma находятся на портах 0 и 1.) См. Также Задержки Haswell AVX / FMA протестированы на 1 цикл медленнее, чем указано в руководстве Intel - задержка обхода может повлиять на каждое использование регистра, пока он не будет перезаписан .

Обратите внимание, что на SnB / IvB (AVX, но не AVX2) только p5 должен обрабатывать логические операции 256b, поскольку vpor ymm, ymm требует AVX2. Вероятно, это не было причиной изменения, поскольку это сделала Нехалем.

Как выбрать с умом:

Имейте в виду, что компиляторы могут использовать por вместо _mm_or_pd, если они хотят, поэтому некоторые из них применимы в основном к рукописному asm. Но некоторые компиляторы в некоторой степени верны выбранным вами встроенным функциям.

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

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

Если вы просто хотите установить / очистить / перевернуть бит в векторах FP между инструкциями FP add и mul, используйте логические логики ...ps даже для данных с двойной точностью, потому что одиночный и двойной FP являются одним и тем же доменом на каждом существующем процессоре, и версии ...ps на один байт короче (без AVX).

Однако есть практические / человеческие причины для использования ...pd версий с внутренними функциями. Важным фактором является удобочитаемость вашего кода для других людей: они будут удивляться, почему вы обрабатываете свои данные как одиночные, хотя на самом деле они удваиваются. Для встроенных функций C / C ++ забрасывать код приведениями между __m128 и __m128d не стоит. (И, надеюсь, компилятор все равно будет использовать orps вместо _mm_or_pd, если он компилируется без AVX, где он фактически сохранит байт.)

Если настройка на уровне выравнивания insn имеет значение, пишите напрямую в asm, а не в intrinsics! (Если инструкция на один байт длиннее, это может лучше согласовать ситуацию с плотностью строк кэша uop и / или декодерами, но с префиксами и режимами адресации вы можете расширять инструкции в целом)

Для целочисленных данных используйте целочисленные версии. Сохранение одного байта инструкции не стоит задержки обхода между paddd или чем-то еще, а целочисленный код часто сохраняет порт 5 полностью занятым перемешиванием. Для Haswell многие инструкции перемешивания / вставки / извлечения / упаковки / распаковки стали только p5, вместо p1 / p5 для SnB / IvB. (Ice Lake наконец-то добавила устройство тасования на другой порт для более распространенных тасовок.)

  1. Эти встроенные функции соответствуют трем различным инструкциям x86 (por, orps, orpd). Есть ли у кого-нибудь идеи, почему Intel тратит драгоценное пространство кода операции на несколько инструкций, которые делают то же самое?

Если вы посмотрите на историю этих наборов инструкций, вы увидите, как мы сюда попали.

por  (MMX):     0F EB /r
orps (SSE):     0F 56 /r
orpd (SSE2): 66 0F 56 /r
por  (SSE2): 66 0F EB /r

MMX существовал до SSE, поэтому похоже, что коды операций для инструкций SSE (...ps) были выбраны из того же 0F xx пространства. Затем для SSE2 версия ...pd добавила префикс размера операнда 66 к коду операции ...ps, а целочисленная версия добавила префикс 66 к версии MMX.

Они могли пропустить orpd и / или por, но они этого не сделали. Возможно, они думали, что будущие проекты ЦП могут иметь более длинные пути пересылки между разными доменами, и поэтому использование инструкции сопоставления для ваших данных будет более сложной задачей. Несмотря на то, что существуют отдельные коды операций, AMD и ранние Intel относились к ним одинаково, как к int-векторам.


Связанный / близкий к дубликату:

person Peter Cordes    schedule 05.07.2015

Согласно рекомендациям Intel и AMD по оптимизации, смешивание типов операций с типами данных приводит к снижению производительности, поскольку ЦП внутренне помечает 64-битные половины регистра для определенного типа данных. Похоже, что это в основном влияет на конвейерную подкладку, поскольку инструкция декодируется и запланированы мопы. Функционально они дают одинаковый результат. Новые версии для целочисленных типов данных имеют более крупную кодировку и занимают больше места в сегменте кода. Поэтому, если размер кода является проблемой, используйте старые операции, поскольку они имеют меньшую кодировку.

person Phernost    schedule 20.08.2010
comment
смешивание типов операций с типами данных приводит к снижению производительности ... Не могли бы вы объяснить это дальше или дальше, дайте мне ссылки на это, спасибо. - person user0002128; 25.01.2013
comment
@ user0002128 это связано с задержкой обхода данных. - person Noah; 16.04.2021