машинный код x86 (MMX / SSE1), 26 байт (4x int16_t)
машинный код x86 (SSE4.1), 28 байт (4x int32_t или uint32_t)
машинный код x86 (SSE2), 24 байта (4x float32) или 27B для cvt int32
(Последняя версия, которая конвертирует int32 в число с плавающей точкой, не совсем точна для больших целых чисел, округляющих до одного и того же числа с плавающей запятой. При вводе с плавающей точкой округление является проблемой вызывающей стороны, и эта функция работает правильно, если нет NaN, идентифицируя числа с плавающей запятой, которые сравнивают == до максимума. Целочисленные версии работают для всех входных данных, рассматривая их как дополнение к знаку 2.)
Все они работают в 16/32/64-битном режиме с одинаковым машинным кодом.
Соглашение о вызове стековых аргументов позволит дважды циклически перебирать аргументы (находить max и затем сравнивать), возможно, давая нам меньшую реализацию, но я не пробовал такой подход.
x86 SIMD имеет вектор-> целочисленное растровое изображение в виде одной инструкции (pmovmskb
или movmskps
или pd), поэтому это было естественно для этого, даже если инструкции MMX / SSE имеют длину не менее 3 байтов. SSSE3 и более поздние инструкции длиннее, чем SSE2, а MMX / SSE1 - самые короткие. Различные версии pmax*
(максимум упакованного целого по вертикали) были представлены в разное время, причем SSE1 (для регистров mmx) и SSE2 (для регистров xmm) имели только знаковое слово (16-битное) и байт без знака.
( pshufw
и pmaxsw
в MMX регистры являются новыми с Katmai Pentium III, поэтому на самом деле они требуют SSE1, а не только бит функции MMX CPU).
Это вызывается из C, как unsigned max4_mmx(__m64)
и для i386 System V ABI, который передает __m64
аргумент mm0
. (Не x86-64 System V, которая проходит__m64
в xmm0
!)
line code bytes
num addr
1 global max4_mmx
2 ;; Input 4x int16_t in mm0
3 ;; output: bitmap in EAX
4 ;; clobbers: mm1, mm2
5 max4_mmx:
6 00000000 0F70C8B1 pshufw mm1, mm0, 0b10110001 ; swap adjacent pairs
7 00000004 0FEEC8 pmaxsw mm1, mm0
8
9 00000007 0F70D14E pshufw mm2, mm1, 0b01001110 ; swap high/low halves
10 0000000B 0FEECA pmaxsw mm1, mm2
11
12 0000000E 0F75C8 pcmpeqw mm1, mm0 ; 0 / -1
13 00000011 0F63C9 packsswb mm1, mm1 ; squish word elements to bytes, preserving sign bit
14
15 00000014 0FD7C1 pmovmskb eax, mm1 ; extract the high bit of each byte
16 00000017 240F and al, 0x0F ; zero out the 2nd copy of the bitmap in the high nibble
17 00000019 C3 ret
size = 0x1A = 26 bytes
Если бы был pmovmskw
, что бы спаслоpacksswb
и и and
(3 + 2 байта). Нам не нужно, and eax, 0x0f
потому что pmovmskb
в регистре MMX уже нули старшие байты. Регистры MMX имеют ширину всего 8 байтов, поэтому 8-битный AL охватывает все возможные ненулевые биты.
Если бы мы знали, что наши входные данные были неотрицательными, мы моглиpacksswb mm1, mm0
бы производить неотрицательные подписанные байты в верхних 4 байтах mm1
, избегая необходимости and
после pmovmskb
. Таким образом, 24 байта.
Пакет x86 с насыщением со знаком обрабатывает ввод и вывод как подписанный, поэтому он всегда сохраняет знаковый бит. ( https://www.felixcloutier.com/x86/packsswb:packssdw ). Интересный факт: пакет x86 с насыщением без знака все еще обрабатывает ввод как подписанный. Это может быть причиной того, что PACKUSDW
не было введено до SSE4.1, в то время как другие 3 комбинации размера и подписи существовали со времен MMX / SSE2.
Или с 32-разрядными целыми числами в регистре XMM (и pshufd
вместо pshufw
) каждой инструкции потребуется еще один байт префикса, за исключением movmskps
замены пакета / и. Но pmaxsd
/pmaxud
нужен дополнительный дополнительный байт ...
вызывается из C, какunsigned max4_sse4(__m128i);
и в x86-64 System V, или в MSVC vectorcall ( -Gv
), оба из которых передают __m128i
/ __m128d
/ __m128
args в регистрах XMM, начиная с xmm0
.
20 global max4_sse4
21 ;; Input 4x int32_t in xmm0
22 ;; output: bitmap in EAX
23 ;; clobbers: xmm1, xmm2
24 max4_sse4:
25 00000020 660F70C8B1 pshufd xmm1, xmm0, 0b10110001 ; swap adjacent pairs
26 00000025 660F383DC8 pmaxsd xmm1, xmm0
27
28 0000002A 660F70D14E pshufd xmm2, xmm1, 0b01001110 ; swap high/low halves
29 0000002F 660F383DCA pmaxsd xmm1, xmm2
30
31 00000034 660F76C8 pcmpeqd xmm1, xmm0 ; 0 / -1
32
33 00000038 0F50C1 movmskps eax, xmm1 ; extract the high bit of each dword
34 0000003B C3 ret
size = 0x3C - 0x20 = 28 bytes
Или, если мы принимаем ввод как float
, мы можем использовать инструкции SSE1. float
Формат может представлять собой широкий диапазон целочисленных значений ...
Или, если вы думаете, что это слишком сильно 0F 5B C0 cvtdq2ps xmm0, xmm0
нарушает правила, начните с 3-байтового преобразования, создав 27-байтовую функцию, которая работает для всех целых чисел, которые точно представлены в виде двоичного кода IEEE32float
, и многих комбинаций входных данных, где некоторые входные данные получают округляется до кратного 2, 4, 8 или любого другого значения во время преобразования. (Таким образом, он на 1 байт меньше, чем версия SSE4.1, и работает на любом x86-64 только с SSE2.)
Если какой-либо из входных данных с плавающей запятой равен NaN, обратите внимание, что он maxps a,b
точно реализуется (a<b) ? a : b
, сохраняя элемент из второго операнда в неупорядоченном виде . Таким образом, для этого может быть возможно возвращение с ненулевым растровым изображением, даже если вход содержит некоторое NaN, в зависимости от того, где они находятся.
unsigned max4_sse2(__m128);
37 global max4_sse2
38 ;; Input 4x float32 in xmm0
39 ;; output: bitmap in EAX
40 ;; clobbers: xmm1, xmm2
41 max4_sse2:
42 ; cvtdq2ps xmm0, xmm0
43 00000040 660F70C8B1 pshufd xmm1, xmm0, 0b10110001 ; swap adjacent pairs
44 00000045 0F5FC8 maxps xmm1, xmm0
45
46 00000048 660F70D14E pshufd xmm2, xmm1, 0b01001110 ; swap high/low halves
47 0000004D 0F5FCA maxps xmm1, xmm2
48
49 00000050 0FC2C800 cmpeqps xmm1, xmm0 ; 0 / -1
50
51 00000054 0F50C1 movmskps eax, xmm1 ; extract the high bit of each dword
52 00000057 C3 ret
size = 0x58 - 0x40 = 24 bytes
copy-and-shuffle с pshufd
- все еще наша лучшая ставка: shufps dst,src,imm8
считывает ввод для нижней половины dst
из dst
. И нам нужно неразрушающее копирование и перемешивание оба раза, так что 3-байт movhlps
иunpckhps
/ pd оба отсутствуют. Если бы мы сужались до скалярного максимума, мы могли бы использовать их, но для трансляции перед сравнением стоит другая инструкция, если у нас уже нет максимума во всех элементах.
Связано: SSE4.1 phminposuw
может найти положение и значение минимума uint16_t
в регистре XMM. Я не думаю, что было бы выгодно вычесть из 65535, чтобы использовать его для max, но вижу SO-ответ об использовании его для max байтов или целых чисел со знаком.