codice macchina x86 (MMX / SSE1), 26 byte (4x int16_t)
codice macchina x86 (SSE4.1), 28 byte (4x int32_t o uint32_t)
codice macchina x86 (SSE2), 24 byte (4x float32) o 27B per cvt int32
(L'ultima versione che converte int32 in float non è perfettamente accurata per numeri interi grandi che arrotondano allo stesso float. Con l'input float, l'arrotondamento è il problema del chiamante e questa funzione funziona correttamente se non ci sono NaN, identificando float che confrontano == al massimo. Le versioni intere funzionano per tutti gli input, trattandoli come complemento firmato 2).
Tutti questi funzionano in modalità 16/32/64 bit con lo stesso codice macchina.
Una convenzione di chiamata di stack-args consentirebbe di eseguire il ciclo degli args due volte (trovare max e quindi confrontare), probabilmente dandoci un'implementazione più piccola, ma non ho provato questo approccio.
x86 SIMD ha bitmap vettoriale-> intero come una singola istruzione ( pmovmskb
o movmskps
o pd), quindi è stato naturale per questo, anche se le istruzioni MMX / SSE sono lunghe almeno 3 byte. Le istruzioni SSSE3 e successive sono più lunghe di SSE2 e le istruzioni MMX / SSE1 sono le più brevi. Sono pmax*
state introdotte versioni diverse di (max verticale intero compresso) in momenti diversi, con SSE1 (per registri mmx) e SSE2 (per registri xmm) con solo parola con segno (16 bit) e byte senza segno.
( pshufw
e pmaxsw
sui registri MMX sono nuovi con Katmai Pentium III, quindi richiedono SSE1, non solo il bit funzione CPU MMX.)
Questo è richiamabile da C come unsigned max4_mmx(__m64)
con l'i386 System V ABI, che passa un __m64
argomento mm0
. (Non x86-64 System V, che passa __m64
in 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
Se ci fosse un pmovmskw
, cosa avrebbe salvato il packsswb
e il and
(3 + 2 byte). Non è necessario and eax, 0x0f
perché pmovmskb
su un registro MMX azzera già i byte superiori. I registri MMX hanno una larghezza di soli 8 byte, quindi AL a 8 bit copre tutti i possibili bit diversi da zero.
Se sapessimo che i nostri input non sono negativi, potremmopacksswb mm1, mm0
produrre byte con segno non negativo nei 4 byte superiori di mm1
, evitando la necessità di and
after pmovmskb
. Quindi 24 byte.
Il pacchetto x86 con saturazione firmata tratta l'input e l'output come firmato, quindi conserva sempre il bit di segno. ( https://www.felixcloutier.com/x86/packsswb:packssdw ). Curiosità: il pacchetto x86 con saturazione non firmata considera ancora l' input come firmato. Questo potrebbe essere il motivo per cui PACKUSDW
non è stato introdotto fino a SSE4.1, mentre le altre 3 combinazioni di dimensioni e firma esistevano da MMX / SSE2.
O con numeri interi a 32 bit in un registro XMM (e pshufd
invece di pshufw
), ogni istruzione avrebbe bisogno di un byte di prefisso in più, ad eccezione della movmskps
sostituzione del pacchetto / e. Ma pmaxsd
/ pmaxud
ho bisogno di un byte extra in più ...
richiamabile da C comeunsigned max4_sse4(__m128i);
con x86-64 System V o MSVC vectorcall ( -Gv
), entrambi i quali passano __m128i
/ __m128d
/ / __m128
args in registri XMM a partire da 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
O se accettiamo input as float
, possiamo usare le istruzioni SSE1. Il float
formato può rappresentare una vasta gamma di valori interi ...
Oppure, se pensi che stia piegando troppo le regole, inizia con un 3 byte 0F 5B C0 cvtdq2ps xmm0, xmm0
da convertire, creando una funzione di 27 byte che funzioni per tutti gli interi che sono esattamente rappresentabili come binario IEEE32 float
e molte combinazioni di input in cui alcuni degli input ottengono arrotondato a un multiplo di 2, 4, 8 o qualsiasi altra cosa durante la conversione. (Quindi è 1 byte più piccolo della versione SSE4.1 e funziona su qualsiasi x86-64 con solo SSE2.)
Se uno qualsiasi degli ingressi float è NaN, nota che maxps a,b
implementa esattamente (a<b) ? a : b
, mantenendo inalterato l'elemento del 2 ° operando . Quindi potrebbe essere possibile che questo ritorni con una bitmap diversa da zero anche se l'input contiene un po 'di NaN, a seconda di dove si trovano.
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
copia-e-mescola con pshufd
è ancora la nostra migliore scommessa: shufps dst,src,imm8
legge l'input per la metà bassa di dst
da dst
. E abbiamo bisogno di una copia e shuffle non distruttiva entrambe le volte, quindi 3 byte movhlps
e unpckhps
/ pd sono entrambi fuori. Se ci limitassimo a un massimo scalare, potremmo usarli, ma costa un'altra istruzione da trasmettere prima del confronto se non abbiamo già il massimo in tutti gli elementi.
Correlati: SSE4.1 phminposuw
può trovare la posizione e il valore del minimo uint16_t
in un registro XMM. Non penso che sia una vittoria sottrarre da 65535 per usarlo per max, ma vedo una risposta SO sull'utilizzo per max di byte o numeri interi con segno.