使用 SSE2 将 unsigned int 钳位为 0x10000

San*_*yin 6 x86 assembly simd sse2 clamp

我想仅使用 SSE2指令将 32 位无符号整数钳制为固定值 (0x10000)。

基本上,这个 C 代码: if (c>0x10000) c=0x10000;

下面的代码有效,但我想知道是否可以简化它,考虑到它是一个特定的常量(0xFFFF+0x0001)

movdqa    xmm3, xmm0 <-- xmm0 contains 4 dword unsigned values
movdqa    xmm4, xmm5 <-- xmm5: four dword 0x10000 values
pxor      xmm3, xmm5
pcmpgtd   xmm4, xmm0
psrad     xmm3, 31
pxor      xmm4, xmm3
pand      xmm0, xmm4
pandn     xmm4, xmm5
por       xmm0, xmm4
Run Code Online (Sandbox Code Playgroud)

的值c在 0x00000000-0xFFFFFFFF 范围内,但假设它在 0x00000000-0x00FFFFFF 或 0x00000000-0x00FF0000 范围内的代码可能是可接受的。

cht*_*htz 6

这是使用饱和加法/减法在全范围内工作的 SSE2 解决方案。它需要 4 个微指令和 2 个常量(和一份副本):

(编辑:对以前版本的微小改进。所需的常量都没有被破坏)

右列描述了如果输入 ( x.h) 的高 16 位为零(在这种情况下x.l需要返回)或不为零(在这种情况下0x10000需要返回)会发生什么情况。

// assumes xmm1 contains 0xffffffff -- can be generated by pcmpeqd
// assumes xmm3 contains 0xfffe0000 -- could be generated by left-shifting a ffffffff vector

                           x.h==0      x.h!=0
    paddusw xmm0, xmm3     [fffe,x.l]  [ffff,x.l]
    movdqa  xmm2, xmm0
    psrld   xmm2, 16       [0000,fffe] [0000,ffff]
    psubw   xmm2, xmm1     [0001,ffff] [0001,0000]
    pand    xmm0, xmm2     [0000,x.l]  [0001,0000]
Run Code Online (Sandbox Code Playgroud)

如果有SSE4.1,当然pminud更简单更好。如果你不需要覆盖 的完整输入范围xmm0,fuz 的解决方案更通用、更容易、更直接(它还有一个稍小的依赖链,并且只需要一个常量向量。)


fuz*_*fuz 5

如果范围可以假设为0x000000000x7fffffff更窄,您可以假装这些值是带符号的,并将序列简化为:

; xmm0 contains 4 dword unsigned values (input)
; xmm5 contains [0x10000, 0x10000, 0x10000, 0x10000]
movdqa    xmm1, xmm5
pcmpgtd   xmm1, xmm0  ; input < 0x10000
pand      xmm0, xmm1  ; input < 0x10000 ? input :       0
pandn     xmm1, xmm5  ; input < 0x10000 ?     0 : 0x10000
por       xmm0, xmm1  ; input < 0x10000 ? input : 0x10000
Run Code Online (Sandbox Code Playgroud)

使用SSE4.1,您可以进一步简化代码,只需

pminud    xmm0, xmm5  ; input < 0x10000 ? input : 0x10000
Run Code Online (Sandbox Code Playgroud)


aqr*_*rit 5

假设它在 0x00000000-0x00FFFFFF 范围内

minps     xmm0, xmm5
Run Code Online (Sandbox Code Playgroud)

如果您尚未在 MXCSR 中设置 DAZ(非正规数为零),则此方法有效。当 DAZ 设置(位1<<6 = 0x40)时,minps视为0x10000正好代表0.0,因此结果为0x00000000

这在某些有用的 CPU 上非常慢(因为微码有助于非规范化),包括第一代 Core 2 Duo (E6600),它具有 SSSE3,但没有 SSE4.1 pminud。测试循环的吞吐量为 1/时钟minps时钟(具有归一化输入),但对于这些次归一化,它平均每个 119 个周期minps。即使在次正常情况下,Skylake 上的速度也很快。

请注意,链接gcc -ffast-math将包括设置 FTZ 和 DAZ 的 CRT 启动代码,因此实际程序可以设置它,而无需执行任何 x86 特定的操作。DAZ 避免了minpsCore 2 等 CPU 的速度下降,但当然也使其对于处理小整数毫无用处。

(FTZ 不影响minps;它不必对其输出进行四舍五入。)


这可能会在 SIMD 整数指令之间产生一些额外的旁路延迟(并且本身具有多周期延迟),但对于吞吐量来说,仍然比 SSE4.1 的 SSE2 模拟更好pminsd/pminud在 CPU 上,由于次正常输入,它不需要微码辅助。

此有限范围内的整数值是有限非负浮点数(IEEE binary32)的位模式。较大的整数位模式表示较大的值,最多可达第一个 NaN ( 0x7F800001)。

此范围内的一半值的指数字段 = 0(位 30:23),因此次正规浮点数也称为非正规浮点数。0x00800000 是最小标准化浮点数的位模式。

  • 很酷的主意。我扩展了答案来解释为什么它有效,以及为什么设置 DAZ 后它不起作用。(我在我的 Skylake CPU 上进行了测试,以确认“minps”在操作数的任一顺序下都会生成“0.0”,前提是它们中的任何一个具有次正规元素。) (2认同)