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 范围内的代码可能是可接受的。
这是使用饱和加法/减法在全范围内工作的 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 的解决方案更通用、更容易、更直接(它还有一个稍小的依赖链,并且只需要一个常量向量。)
如果范围可以假设为0x00000000
或0x7fffffff
更窄,您可以假装这些值是带符号的,并将序列简化为:
; 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)
假设它在 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 避免了minps
Core 2 等 CPU 的速度下降,但当然也使其对于处理小整数毫无用处。
(FTZ 不影响minps
;它不必对其输出进行四舍五入。)
这可能会在 SIMD 整数指令之间产生一些额外的旁路延迟(并且本身具有多周期延迟),但对于吞吐量来说,仍然比 SSE4.1 的 SSE2 模拟更好pminsd
/pminud
在 CPU 上,由于次正常输入,它不需要微码辅助。
此有限范围内的整数值是有限非负浮点数(IEEE binary32)的位模式。较大的整数位模式表示较大的值,最多可达第一个 NaN ( 0x7F800001
)。
此范围内的一半值的指数字段 = 0(位 30:23),因此次正规浮点数也称为非正规浮点数。0x00800000 是最小标准化浮点数的位模式。