Code machine x86 (MMX / SSE1), 26 octets (4x int16_t)
code machine x86 (SSE4.1), 28 octets (4x int32_t ou uint32_t)
code machine x86 (SSE2), 24 octets (4x float32) ou 27B vers cvt int32
(La dernière version qui convertit int32 en float n'est pas parfaitement précise pour les grands entiers qui arrondissent au même float. Avec l'entrée float, l'arrondi est le problème de l'appelant et cette fonction fonctionne correctement s'il n'y a pas de NaN, identifiant les flotteurs qui comparent == au maximum. Les versions entières fonctionnent pour toutes les entrées, les traitant comme des compléments signés 2.)
Tous ces éléments fonctionnent en mode 16/32/64 bits avec le même code machine.
Une convention d'appel stack-args permettrait de faire une boucle sur les arguments deux fois (trouver max puis comparer), ce qui pourrait nous donner une implémentation plus petite, mais je n'ai pas essayé cette approche.
x86 SIMD a vector-> bitmap entier comme une seule instruction ( pmovmskb
ou movmskps
ou pd), il était donc naturel pour cela même si les instructions MMX / SSE sont longues d'au moins 3 octets. Les instructions SSSE3 et ultérieures sont plus longues que SSE2 et les instructions MMX / SSE1 sont les plus courtes. Différentes versions de pmax*
(vertical à nombres entiers compressés max) ont été introduites à des moments différents, SSE1 (pour les registres mmx) et SSE2 (pour les registres xmm) ayant uniquement un mot signé (16 bits) et un octet non signé.
( pshufw
et pmaxsw
sur les registres MMX sont nouveaux avec Katmai Pentium III, donc ils nécessitent vraiment SSE1, pas seulement le bit de fonctionnalité CPU MMX.)
Cela peut être appelé à partir de C comme unsigned max4_mmx(__m64)
avec l'i386 System V ABI, qui transmet un __m64
argument mm0
. (Non x86-64 System V, qui passe __m64
en 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
S'il y avait un pmovmskw
, ce qui aurait sauvé le packsswb
et le and
(3 + 2 octets). Nous n'en avons pas besoin and eax, 0x0f
car pmovmskb
sur un registre MMX, les zéros supérieurs sont déjà zéros. Les registres MMX ne font que 8 octets de large, donc l'AL 8 bits couvre tous les bits non nuls possibles.
Si nous savions que nos entrées n'étaient pas négatives, nous pourrionspacksswb mm1, mm0
produire des octets signés non négatifs dans les 4 octets supérieurs de mm1
, en évitant le besoin d' and
after pmovmskb
. Donc 24 octets.
Le pack x86 avec saturation signée traite l'entrée et la sortie comme signées, donc il préserve toujours le bit de signe. ( https://www.felixcloutier.com/x86/packsswb:packssdw ). Fait amusant: le pack x86 avec saturation non signée traite toujours l' entrée comme signée. Cela pourrait être la raison pour laquelle il PACKUSDW
n'a pas été introduit avant SSE4.1, alors que les 3 autres combinaisons de taille et de signature existaient depuis MMX / SSE2.
Ou avec des entiers 32 bits dans un registre XMM (et pshufd
au lieu de pshufw
), chaque instruction aurait besoin d'un octet de préfixe de plus, sauf pour movmskps
remplacer le pack / et. Mais pmaxsd
/ pmaxud
besoin d'un octet supplémentaire ...
appelable à partir de C commeunsigned max4_sse4(__m128i);
avec x86-64 System V, ou MSVC vectorcall ( -Gv
), qui passent __m128i
/ __m128d
/ __m128
args dans les regs XMM commençant par 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
Ou si nous acceptons la saisie en tant que float
, nous pouvons utiliser les instructions SSE1. Le float
format peut représenter une large gamme de valeurs entières ...
Ou si vous pensez que cela déforme trop les règles, commencez par un octet 0F 5B C0 cvtdq2ps xmm0, xmm0
à convertir, créant une fonction de 27 octets qui fonctionne pour tous les entiers qui sont exactement représentables en tant que binaire IEEE32 float
, et de nombreuses combinaisons d'entrées où certaines entrées obtiennent arrondi à un multiple de 2, 4, 8 ou autre lors de la conversion. (Il est donc 1 octet plus petit que la version SSE4.1 et fonctionne sur n'importe quel x86-64 avec seulement SSE2.)
Si l'une des entrées flottantes est NaN, notez qu'elle maxps a,b
implémente exactement (a<b) ? a : b
, en gardant l'élément du 2e opérande non ordonné . Il pourrait donc être possible pour cela de retourner avec un bitmap non nul même si l'entrée contient du NaN, selon l'endroit où ils se trouvent.
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
copier-mélanger avec pshufd
est toujours notre meilleur pari: shufps dst,src,imm8
lit l'entrée pour la moitié inférieure dst
de dst
. Et nous avons besoin d'un copier-shuffle non destructif les deux fois, donc 3 octets movhlps
et unpckhps
/ pd sont tous les deux sortis. Si nous nous limitions à un maximum scalaire, nous pourrions les utiliser, mais cela coûte une autre instruction de diffuser avant de comparer si nous n'avons pas déjà le maximum dans tous les éléments.
Connexes: SSE4.1 phminposuw
peut trouver la position et la valeur du minimum uint16_t
dans un registre XMM. Je ne pense pas que ce soit une victoire de soustraire de 65535 pour l'utiliser pour max, mais voir une réponse SO sur son utilisation pour max d'octets ou d'entiers signés.