_MM_SHUFFLE
сначала берет старший элемент, поэтому _MM_SHUFFLE(3,3, 1,1)
будет делать movshdup
shuffle.
Основное отличие - на уровне сборки; movshdup
- это функция копирования-и-перемешивания, избегающая movaps
для копирования ввода, если ввод a
все еще требуется позже (например, как часть горизонтальной суммы: см. Самый быстрый способ сделать горизонтальный вектор с плавающей точкой) сумма на x86 для примера того, как он компилируется без movaps
против версии SSE1, которая использует shufps
.
movshdup
/ movsldup
также может быть загрузкой + перемешиванием с операндом источника памяти. (shufps
, очевидно, не может, потому что ему требуется один и тот же вход дважды.) На современных процессорах Intel (семейство Sandybridge) movshdup xmm0, [rdi]
декодирует до чистой нагрузки UOP, а не с микросинхронизацией с ALU UOP . Таким образом, он не конкурирует за пропускную способность ALU (порт 5) с другими шаффлами. Порты загрузки содержат логику для широковещательной загрузки (включая movddup
64-битную широковещательную передачу) и movs[lh]dup
дублирование пар элементов. Более сложные load + shuffle, такие как vpermilps xmm0, [rdi], 0x12
или pshufd xmm, [rdi], 0x12
, по-прежнему декодируют на несколько мопов, возможно, микросжатые в load + ALU в зависимости от uarch.
Обе инструкции имеют одинаковую длину: movshdup
избегает непосредственного байта, но shufps
является инструкцией SSE1, поэтому она имеет только 2-байтовый код операции, на 1 байт короче, чем инструкции SSE2 и SSE3. Но с включенным AVX vmovshdup
действительно сохраняет байт , потому что преимущество размера кода исчезает.
В более старых процессорах с только 64-разрядными модулями случайного воспроизведения (такими как Pentium-M и Core 2 первого поколения (Merom)) было более значительное преимущество в производительности . movshdup
только тасует в пределах 64-битных половинок вектора. В Core 2 Merom movshdup xmm, xmm
декодирует до 1 моп, а shufps xmm, xmm, i
декодирует до 3 моп. (См. https://agner.org/optimize/ для таблиц инструкций и руководства микроархива). См. Также мой ответ по горизонтальной сумме (связанный ранее) для получения дополнительной информации о процессорах SlowShuffle, таких как Merom и K8.
В C ++ со встроенными функциями
Если SSE3 включен, оптимизация будет пропущена, если ваш компилятор не оптимизирует _mm_shuffle_ps(a, a, _MM_SHUFFLE(3, 3, 1, 1))
в ту же сборку, которую он сделал бы для _mm_movehdup_ps(a)
.
Некоторые компиляторы (например, MSVC), как правило, не оптимизируют intriniscs, поэтому программист должен понять, как избежать использования movaps
инструкций, используя встроенные функции для команд копирования и перестановки (например, pshufd
) и movshdup
) вместо тасов, которые обязательно уничтожают регистр назначения (например, shufps
и, например, psrldq
byte-shift.)
Кроме того, MSVC не позволяет вам использовать SSE3 компилятором, вы получаете инструкции за пределами базового SSE2 (или без SIMD), если вы используете для них встроенные функции. Или, если вы включите AVX, это позволит компилятору использовать SSE4.2 и более ранние версии, но он все же решит не оптимизировать. Итак, еще раз, до человека-программиста, чтобы найти оптимизации. ICC похож. Иногда это может быть хорошо, если вы знаете точно , что вы делаете, и проверяете вывод asm компилятора, потому что иногда оптимизации gcc или clang могут пессимизировать ваш код.
Вероятно, хорошая идея собрать с clang и посмотреть, использует ли он те же инструкции, что и встроенные в ваш источник; на сегодняшний день он обладает лучшим оптимизатором случайного выбора из всех 4 основных компиляторов, которые поддерживают встроенные функции Intel, в основном оптимизируя код встроенных функций так же, как компиляторы обычно оптимизируют чистый C, то есть просто следуя правилу «как если» для получения того же результата.
Самый тривиальный пример:
#include <immintrin.h>
__m128 shuf1(__m128 a) {
return _mm_shuffle_ps(a,a, _MM_SHUFFLE(3,3, 1,1));
}
скомпилировано с gcc / clang / MSVC / ICC на Godbolt
GCC и clang с -O3 -march=core2
оба замечают оптимизацию:
shuf1:
movshdup xmm0, xmm0
ret
ICC -O3 -march=haswell
и MSVC -O2 -arch:AVX -Gv
(для включения соглашения о вызовах векторного вызова вместо передачи векторов SIMD по ссылке.)
shuf1:
vshufps xmm0, xmm0, xmm0, 245 #4.12
ret #4.12