kode mesin x86 (MMX / SSE1), 26 byte (4x int16_t)
kode mesin x86 (SSE4.1), 28 byte (4x int32_t atau uint32_t)
kode mesin x86 (SSE2), 24 byte (4x float32) atau 27B ke cvt int32
(Versi terakhir yang mengonversi int32 ke float tidak sepenuhnya akurat untuk bilangan bulat besar yang bulat ke float yang sama. Dengan input float, pembulatan adalah masalah pemanggil dan fungsi ini berfungsi dengan benar jika tidak ada NaN, mengidentifikasi float yang membandingkan == maks. Versi integer bekerja untuk semua input, memperlakukannya sebagai pelengkap yang ditandatangani 2).
Semua ini bekerja dalam mode 16/32/64-bit dengan kode mesin yang sama.
Konvensi pemanggilan stack-args akan memungkinkan untuk mengulang argumen dua kali (menemukan maks dan kemudian membandingkan), mungkin memberi kami implementasi yang lebih kecil, tetapi saya belum mencoba pendekatan itu.
x86 SIMD memiliki bitmap vektor-> integer sebagai instruksi tunggal ( pmovmskb
atau movmskps
pd), jadi itu wajar untuk ini meskipun instruksi MMX / SSE setidaknya sepanjang 3 byte. Instruksi SSSE3 dan yang lebih baru lebih panjang dari pada SSE2, dan instruksi MMX / SSE1 adalah yang terpendek. Versi berbeda pmax*
(packed-integer vertical max) diperkenalkan pada waktu yang berbeda, dengan SSE1 (untuk mmx regs) dan SSE2 (untuk xmm regs) hanya memiliki kata yang ditandatangani (16-bit) dan byte yang tidak ditandatangani.
( pshufw
dan pmaxsw
pada register MMX masih baru dengan Katmai Pentium III, jadi mereka benar-benar membutuhkan SSE1, bukan hanya fitur CPU MMX.)
Ini bisa dipanggil dari C seperti unsigned max4_mmx(__m64)
halnya i386 System V ABI, yang meneruskan __m64
argumen in mm0
. (Tidak x86-64 Sistem V, yang melewati __m64
di 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
Jika ada pmovmskw
, apa yang akan menyelamatkan packsswb
dan and
(3 + 2 byte). Kami tidak perlu and eax, 0x0f
karena pmovmskb
pada register MMX sudah nol byte atas. Register MMX hanya memiliki lebar 8 byte, jadi 8-bit AL mencakup semua bit yang tidak nol.
Jika kita tahu input kita non-negatif, kita bisapacksswb mm1, mm0
menghasilkan byte yang ditandatangani non-negatif di atas 4 byte mm1
, menghindari kebutuhan untuk and
setelah pmovmskb
. Jadi 24 byte.
paket x86 dengan saturasi yang telah ditandatangani memperlakukan input dan output sebagai yang ditandatangani, sehingga selalu mempertahankan bit tanda. ( https://www.felixcloutier.com/x86/packsswb:packssdw ). Fakta menyenangkan: paket x86 dengan saturasi yang tidak ditandatangani masih memperlakukan input yang ditandatangani. Ini mungkin mengapa PACKUSDW
tidak diperkenalkan sampai SSE4.1, sementara 3 kombinasi ukuran dan signness lainnya ada sejak MMX / SSE2.
Atau dengan bilangan bulat 32-bit dalam register XMM (dan pshufd
bukannya pshufw
), setiap instruksi akan membutuhkan satu byte awalan lagi, kecuali untuk movmskps
mengganti paket / dan. Tapi pmaxsd
/ pmaxud
membutuhkan byte tambahan ekstra ...
callable dari C sebagaiunsigned max4_sse4(__m128i);
dengan x86-64 System V, atau MSVC vectorcall ( -Gv
), yang keduanya lulus __m128i
/ __m128d
/ __m128
args di regs XMM dimulai dengan 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
Atau jika kami menerima input float
, kami dapat menggunakan instruksi SSE1. The float
Format dapat mewakili berbagai nilai integer ...
Atau jika Anda berpikir bahwa membengkokkan aturan terlalu jauh, mulailah dengan 3-byte 0F 5B C0 cvtdq2ps xmm0, xmm0
untuk dikonversi, membuat fungsi 27-byte yang bekerja untuk semua bilangan bulat yang benar-benar mewakili IEEE binary32 float
, dan banyak kombinasi input di mana beberapa input mendapatkan dibulatkan menjadi kelipatan 2, 4, 8, atau apa pun selama konversi. (Jadi 1 byte lebih kecil dari versi SSE4.1, dan bekerja pada x86-64 apa saja hanya dengan SSE2.)
Jika salah satu dari input float adalah NaN, perhatikan bahwa maxps a,b
mengimplementasikan dengan tepat (a<b) ? a : b
, menjaga elemen dari operan ke-2 pada unordered . Jadi dimungkinkan untuk kembali dengan bitmap yang tidak nol bahkan jika inputnya mengandung beberapa NaN, tergantung di mana bitmap itu berada.
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
salin-dan-kocok dengan pshufd
masih taruhan terbaik kami: shufps dst,src,imm8
membaca input dari setengah rendah dst
dari dst
. Dan kita perlu copy-and-shuffle non-destruktif kedua kali, sehingga 3-byte movhlps
dan unpckhps
/ pd keduanya keluar. Jika kita mempersempit ke skalar maks, kita bisa menggunakan itu, tetapi membutuhkan instruksi lain untuk disiarkan sebelum membandingkan jika kita belum memiliki maks dalam semua elemen.
Terkait: SSE4.1 phminposuw
dapat menemukan posisi dan nilai minimum uint16_t
dalam register XMM. Saya tidak berpikir itu menang untuk mengurangi dari 65.535 untuk menggunakannya untuk maks, tetapi lihat jawaban SO tentang menggunakannya untuk maks byte atau bilangan bulat yang ditandatangani.