x86 makine kodu (MMX / SSE1), 26 bayt (4x int16_t)
x86 makine kodu (SSE4.1), 28 bayt (4x int32_t veya uint32_t)
x86 makine kodu (SSE2), 24 bayt (4x float32) veya cvt int32 için 27B
(İnt32'yi float'a dönüştüren son sürüm, aynı float'a yuvarlanan büyük tamsayılar için mükemmel bir şekilde doğru değildir. Float girişi ile yuvarlama, arayanın sorunudur ve NaN'ler yoksa, bu işlev doğru şekilde çalışır; Tamsayı sürümleri, tüm girdiler için imzalanmış 2'nin tamamlayıcısı olarak kabul edilir.)
Tüm bunlar aynı makine koduyla 16/32/64 bit modunda çalışır.
Bir yığın-args çağırma kuralı, muhtemelen daha küçük bir uygulama vererek, iki kez argümanlar üzerinde döngü (max bulma ve sonra karşılaştırma) mümkün kılar, ama bu yaklaşımı denemedim.
x86 SIMD'de tek bir komut olarak vektör-> tamsayı bitmap (pmovmskb
veya movmskps
pd) , bu nedenle MMX / SSE komutları en az 3 bayt uzunluğunda olsa bile bunun için doğaldı. SSSE3 ve sonraki talimatlar SSE2'den daha uzun ve MMX / SSE1 talimatları en kısa olanıdır. pmax*
SSE1 (mmx regs için) ve SSE2 (xmm regs için) yalnızca imzalı kelimeye (16 bit) ve imzasız bayta sahip olmak üzere farklı zamanlarda (paketli tamsayı dikey maks) farklı sürümler tanıtıldı.
( pshufw
ve pmaxsw
MMX kayıtlarında Katmai Pentium III'te yenidir, bu yüzden sadece MMX CPU özellik biti için değil, SSE1 gerektirirler.)
Bu C olarak çağrılabilir unsigned max4_mmx(__m64)
bir geçer i386 Sistem V ABI ile __m64
Arg mm0
. (Geçen x86-64 Sistem V değil,__m64
içinde 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
Eğer bir pmovmskw
şey olsaydı,packsswb
ve and
(3 + 2 bayt) . İhtiyacımız yok and eax, 0x0f
çünkü pmovmskb
bir MMX kaydında zaten üst baytları sıfırlar. MMX kayıtları yalnızca 8 bayt genişliğindedir, bu nedenle 8 bit AL olası tüm sıfır olmayan bitleri kapsar.
Girdilerimizin negatif olmadığını bilseydikpacksswb mm1, mm0
, üst 4 baytta negatif olmayan işaretli bayt üretebiliriz mm1
, and
sonradan ihtiyacını ortadan kaldırabilirizpmovmskb
. Böylece 24 bayt.
İmzalı doygunluğa sahip x86 paketi, girdi ve çıktıyı imzalı olarak ele alır, böylece her zaman işaret bitini korur. ( https://www.felixcloutier.com/x86/packsswb:packssdw ). İlginç gerçek: İmzasız doygunluğa sahip x86 paketi, girdiyi hala imzalanmış olarak kabul eder . Bu nedenle PACKUSDW
SSE4.1'e kadar kullanılmamış olabilirken , diğer 3 boyut ve imza kombinasyonu MMX / SSE2'den beri mevcuttu.
Veya bir XMM kaydındaki 32 bit tamsayılarla (ve pshufd
bunun yerine pshufw
), her komutun movmskps
/ ve yerine başka bir önek baytına ihtiyacı olacaktır . Ama pmaxsd
/ pmaxud
ekstra ekstra bayt gerekir ...
unsigned max4_sse4(__m128i);
x86-64 System V veya MSVC vectorcall ( -Gv
) ile olduğu gibi C'den çağrılabilir , her ikisi de XMM kayıtlarında başlayan __m128i
/ __m128d
/ __m128
args ile başlar 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
Veya girişi olarak kabul float
edersek, SSE1 talimatlarını kullanabiliriz. float
Biçim tamsayı değerlerinin geniş bir yelpazede temsil edebilir ...
Veya kuralları çok fazla büktüğünü düşünüyorsanız, 3 baytla başlayın 0F 5B C0 cvtdq2ps xmm0, xmm0
büktüğünü düşünüyorsanız float
, dönüştürmek ile başlayın , IEEE ikili dosyası olarak tam olarak temsil edilebilen tüm tamsayılar ve bazı girdilerin aldığı birçok girdi kombinasyonu için çalışan 27 baytlık bir işlev yapın. dönüşüm sırasında 2, 4, 8 veya katlarına yuvarlanır. (Yani SSE4.1 sürümünden 1 bayt daha küçüktür ve sadece SSE2 ile herhangi bir x86-64 üzerinde çalışır.)
Şamandıra girdilerin herhangi NaN, not ise o maxps a,b
tam uygular (a<b) ? a : b
, sırasız üzerinde 2 İşlenenden eleman tutarak . Bu nedenle, giriş, bulundukları yere bağlı olarak bazı NaN içeriyor olsa bile sıfır olmayan bir bitmap ile geri dönmek mümkün olabilir.
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
shuffle'ınızı kopyalayıp pshufd
hala en iyi bahis: shufps dst,src,imm8
düşük yarısı için giriş okur dst
dan dst
. Her iki seferde de tahribatsız bir kopyala-karıştır seçeneğine ihtiyacımız var, bu yüzden 3 baytmovhlps
ve unpckhps
/ pd ikisi de dışarıda. Bir skaler maks'e daralsaydık, bunları kullanabilirdik, ancak zaten tüm öğelerde maksimum değere sahip değiliz, karşılaştırmadan önce yayınlamak için başka bir talimat gerekiyor.
İlgili: SSE4.1 phminposuw
, uint16_t
bir XMM kaydında minimumun konumunu ve değerini bulabilir . Maksimum için kullanmak için 65535'ten çıkarmanın bir kazanç olduğunu düşünmüyorum, ancak maksimum bayt veya işaretli tamsayılar için kullanma hakkında bir SO cevabı görüyorum .