машинний код x86 (MMX / SSE1), 26 байт (4x int16_t)
машинний код x86 (SSE4.1), 28 байт (4х int32_t або uint32_t)
машинний код x86 (SSE2), 24 байти (4x float32) або 27B в cvt int32
(Остання версія, яка перетворює int32 у плаваючий, не є абсолютно точною для великих цілих чисел, які округляються до одного плаваючого елемента. З введенням поплавця округлення є проблемою виклику, і ця функція працює правильно, якщо немає NaN, ідентифікуючи поплавці, які порівнюють == до максимуму цілі версії працюють для всіх входів, трактуючи їх як підписані доповнення 2.)
Усі вони працюють в 16/32/64-бітному режимі з тим же машинним кодом.
Конвенція, що викликає стеки, дає змогу двічі перебирати аргументи (знаходячи макс, а потім порівнюючи), можливо, даючи нам меншу реалізацію, але я не пробував такого підходу.
x86 SIMD має векторні> цілісні растрові карти як єдину інструкцію ( pmovmskb
або movmskps
pd), тому це було природно, навіть якщо інструкції MMX / SSE мають принаймні 3 байти. Інструкції SSSE3 та новіших версій довші, ніж SSE2, а інструкції MMX / SSE1 - найкоротші. Різні версії pmax*
(пакували цілочисельні вертикальні максимуми) були введені в різний час, при цьому SSE1 (для regx mmx) і SSE2 (для xmm reg) мали лише підписане слово (16-бітне) та непідписаний байт.
( pshufw
а pmaxsw
регістри MMX є новими в Katmai Pentium III, тому вони дійсно потребують SSE1, а не лише біт функції процесора MMX.)
Це можна викликати з C, як unsigned max4_mmx(__m64)
у системі i386 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
арг в 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
Формат може являти собою широкий діапазон цілочисельних значень ...
Або якщо ви вважаєте, що це загинає правила занадто далеко, почніть з 3-байтового 0F 5B C0 cvtdq2ps xmm0, xmm0
перетворення, зробивши 27-байтну функцію, яка працює для всіх цілих чисел, які точно представлені як IEEE binary32 float
, і багатьох комбінацій входів, де деякі входи отримують округлене до кратного 2, 4, 8 або будь-якого іншого під час перетворення. (Отже, це на 1 байт менше, ніж версія SSE4.1, і працює на будь-якому x86-64 лише з SSE2.)
Якщо будь-який з поплавкових входів є NaN, зауважте, що він maxps a,b
точно реалізує (a<b) ? a : b
, зберігаючи елемент від 2-го операнда не упорядкованим . Таким чином, можливо, це може повернутися з ненульовою растровою картою, навіть якщо вхід містить деякий 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
копіювати і перетасовувати з pshufd
нами все ще найкраща ставка: shufps dst,src,imm8
читається вхід для нижньої половини dst
з dst
. І нам потрібні неруйнівні копії та переміщення обох разів, тому 3-байт movhlps
і unpckhps
/ pd обидва вийшли. Якби ми звужувались до скалярного максимуму, ми могли б використовувати їх, але це коштує ще одну інструкцію для трансляції перед порівнянням, якщо у нас уже немає макс у всіх елементах.
Пов'язано: SSE4.1 phminposuw
може знайти положення та значення мінімуму uint16_t
в регістрі XMM. Я не думаю, що виграти від 65535 виграш, щоб використовувати його для max, але дивіться SO відповідь про його використання для max байтів або підписаних цілих чисел.