В чем разница между логическими SSE? - PullRequest
15 голосов
/ 10 мая 2010

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

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

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

Ответы [ 3 ]

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

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

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

См. этот ответ или документ микроархитектуры Agner Fog для обходных задержек. Ищите «Задержки обхода данных на Nehalem» в документе Агнера; у него есть несколько хороших практических примеров и дискуссий. У него есть раздел, посвященный каждому микроарху, которого он проанализировал.

Однако задержки для передачи данных между разные домены или разные типы регистров меньше на Песчаный мост и мост плюща, чем на Нехалеме, и часто ноль. - Микро арка Агнера Фога документ

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

2: Версия ...ps занимает на 1 байт кода меньше, чем две другие. Это выровняет следующие инструкции по-разному, что может иметь значение для декодеров и / или строк кэша UOP.

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

  • Merom (Core2) и Penryn: orps могут работать на p0 / p1 / p5, но только с целочисленными доменами. Предположительно все 3 версии раскодированы в один и тот же моп. Так что задержка междоменной пересылки происходит. (Процессоры 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 на цикл . Информация о задержках пересылки пока недоступна.

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

Как правильно выбирать :

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

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

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

Существуют практические / человеческие причины для использования версий ...pd, которые часто перевешивают экономию 1 байта кода. Читаемость вашего кода другими людьми является одним из факторов: они будут удивляться, почему вы относитесь к своим данным как к одиночным, когда они фактически удваиваются. Особенно при использовании C / C ++ засорение вашего кода приведениями между __mm256 и __mm256d не стоит. Если настройка на уровне выравнивания insn имеет значение, пишите прямо в asm, а не по внутреннему! (Если инструкция будет на один байт длиннее, это может лучше выровнять данные для плотности строк кэша UOP и / или декодеров.)

Для целочисленных данных используйте целочисленные версии. Сохранение одного байта инструкции не стоит задержки обхода, а целочисленный код часто держит порт 5 полностью занятым перемешиваниями. Для Haswell многие инструкции shuffle / insert / extract / pack / unpack стали только p5 вместо p1 / p5 для SnB / IvB.

  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-vector.

7 голосов
/ 20 августа 2010

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

3 голосов
/ 10 мая 2010

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

#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
...