x86机器代码(MMX / SSE1),26字节(4x int16_t)
x86机器码(SSE4.1),28字节(4x int32_t或uint32_t)
x86机器代码(SSE2),24字节(4x float32)或27B到cvt int32
(将int32转换为float的最后一个版本对于舍入到相同float的大整数并不是十分准确。使用float输入,舍入是调用者的问题,如果没有NaN,此函数将正常工作,以标识可比较==的float整数版本适用于所有输入,将其视为有符号2的补码。)
所有这些均以相同的机器代码在16/32/64位模式下工作。
使用stack-args调用约定可以使args循环两次(查找max然后进行比较),这可能为我们提供了一个较小的实现,但是我没有尝试过这种方法。
x86 SIMD将vector-> integer位图作为单个指令(pmovmskb或movmskpspd)使用,因此即使MMX / SSE指令的长度至少为3个字节也很自然。SSSE3和更高版本的指令比SSE2长,而MMX / SSE1指令最短。pmax*在不同的时间引入了不同版本的(最大整数垂直压缩),其中SSE1(用于mmx regs)和SSE2(用于xmm regs)仅具有带符号字(16位)和无符号字节。
(pshufw以及pmaxswMMX寄存器是Katmai Pentium III的新增功能,因此实际上它们需要SSE1,而不仅仅是MMX CPU功能位。)
unsigned max4_mmx(__m64)与i386 System V ABI一样,它可以从C调用,后者在中传递__m64arg mm0。(不是x86-64 System V,它通过__m64了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
如果有一个pmovmskw,将保存packsswb和和and(3 + 2个字节)的内容。我们不需要,and eax, 0x0f因为pmovmskb在MMX寄存器上已经将高字节清零了。MMX寄存器只有8个字节宽,因此8位AL覆盖了所有可能的非零位。
如果我们知道输入是非负的,则可以packsswb  mm1, mm0在的高4个字节中生成非负的有符号字节,从而mm1避免了需要andafter pmovmskb。  因此为24个字节。
带符号饱和的x86 pack将输入和输出视为带符号,因此它始终保留符号位。(https://www.felixcloutier.com/x86/packsswb:packssdw)。有趣的事实:具有无符号饱和的x86 pack仍将输入视为有符号。这可能就是为什么PACKUSDW直到SSE4.1才引入的原因,而自MMX / SSE2以来,存在尺寸和签名的其他3种组合。
或在XMM寄存器中使用32位整数(而pshufd不是pshufw),每条指令都需要再加上一个前缀字节,除了movmskps替换pack / and。但是pmaxsd/ pmaxud需要一个额外的额外字节...
可调用从C作为unsigned max4_sse4(__m128i);与x86-64的系统V,或MSVC vectorcall( -Gv),这两者都通过__m128i/ __m128d/ __m128在XMM暂存器ARGS开始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
或者,如果我们接受输入为float,则可以使用SSE1指令。该float格式可以表示各种整数值...
或者,如果您认为规则过分弯曲,请从3字节0F 5B C0             cvtdq2ps  xmm0, xmm0开始转换,然后制作一个27字节的函数,该函数适用于所有可完全表示为IEEE binary32的整数float,以及许多输入组合,其中一些输入可以在转换过程中四舍五入为2、4、8或其他倍数。(因此,它比SSE4.1版本小1个字节,并且可以在仅具有SSE2的任何x86-64上运行。)
如果任何浮点输入为NaN,请注意maxps a,b完全实现(a<b) ? a : b,将第二个操作数中的元素保持为无序。因此,即使输入包含某些NaN(取决于它们的位置),也可能返回非零位图。
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
复制和随机播放with pshufd仍然是我们最好的选择:shufps dst,src,imm8读取dst from中 下半部分的输入dst。而且我们都需要非破坏性的复制和混洗,因此3字节movhlps和unpckhps/ pd都消失了。如果将范围缩小到标量最大值,则可以使用它们,但是如果我们还没有在所有元素中都具有最大值,则在比较之前要花费另一条指令进行广播。
相关信息:SSE4.1 phminposuw可以uint16_t在XMM寄存器中找到最小值的位置和值。我认为从65535中减去以将其用于最大数并不是一个胜利,但是看到关于将其用于最大字节数或带符号整数的SO答案。