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 ( pmovmskbo movmskpso 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.
( pshufwe pmaxswsui 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 __m64argomento mm0. (Non x86-64 System V, che passa __m64in 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 packsswbe il and(3 + 2 byte). Non è necessario and eax, 0x0fperché pmovmskbsu 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 andafter 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 PACKUSDWnon è 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 pshufdinvece di pshufw), ogni istruzione avrebbe bisogno di un byte di prefisso in più, ad eccezione della movmskpssostituzione del pacchetto / e. Ma pmaxsd/ pmaxudho 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/ / __m128args 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 floatformato 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, xmm0da convertire, creando una funzione di 27 byte che funzioni per tutti gli interi che sono esattamente rappresentabili come binario IEEE32 floate 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,bimplementa 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,imm8legge 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 movhlpse 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 phminposuwpuò trovare la posizione e il valore del minimo uint16_tin 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.