我有一个 4D 向量:std::array
我想检查它的所有组件是否都在值范围内:0.0f<= X && X < 256.0f
如何检查向量分量是否超出此范围?我所需要的只是一个布尔值来知道整个向量是否通过测试。
我第一次尝试解决这个问题是下面的代码:
bool Check_If_Outside_2(std::array<float, 4> vec)
{
bool outside = false;
for (int i = 0; i < 4; i++)
if (vec[i] < VType(0) || vec[i] >= VType(256))
outside = true;
return outside;
}
这导致了这个汇编器输出:
Check_If_Outside_2(std::array<float, 4ul>): # @Check_If_Outside_2(std::array<float, 4ul>)
vxorps xmm2, xmm2, xmm2
vucomiss xmm0, xmm2
setb al
vmovss xmm3, dword ptr [rip + .LCPI7_0] # xmm3 = mem[0],zero,zero,zero
vucomiss xmm0, xmm3
setae cl
or cl, al
vmovshdup xmm0, xmm0 # xmm0 = xmm0[1,1,3,3]
vucomiss xmm0, xmm2
setb dl
vucomiss xmm0, xmm3
setae al
or al, dl
or al, cl
vxorps xmm0, xmm0, xmm0
vcmpltps xmm0, xmm1, xmm0
vpermilps xmm0, xmm0, 212 # xmm0 = xmm0[0,1,1,3]
vmovaps xmm2, xmmword ptr [rip + .LCPI7_1] # xmm2 = <2.56E+2,2.56E+2,u,u>
vcmpleps xmm1, xmm2, xmm1
vpermilps xmm1, xmm1, 212 # xmm1 = xmm1[0,1,1,3]
vorps xmm0, xmm0, xmm1
vpsllq xmm0, xmm0, 63
vmovmskpd ecx, xmm0
or al, cl
shr cl
or al, cl
and al, 1
ret
然后我尝试了以下优化版本,它使用负整数值填充寄存器中的最高位的想法。因此,我只需将浮点数转换为整数,并测试高位是否有非零。如果存在非零位,则向量的分量要么为负,要么高于合法的最大范围。
template<typename T, unsigned long N>
static inline std::array<int32_t, N> To_Int_Vec(const std::array<T, N>& x)
{
std::array<int32_t, N> int_vec;
for (int i = 0; i < N; ++i)
int_vec[i] = floor(x[i]);
return int_vec;
}
bool Check_If_Outside(std::array<float, 4> vec)
{
constexpr int32_t neg_mask = ~255;
auto vec_int = To_Int_Vec(vec);
bool outside = false;
for (int i = 0; i < 4; i++)
if (vec_int[i] & neg_mask)
outside = true;
return outside;
}
这给出了以下汇编器输出:
Check_If_Outside(std::array<float, 4ul>): # @Check_If_Outside(std::array<float, 4ul>)
vshufps xmm0, xmm0, xmm1, 65 # xmm0 = xmm0[1,0],xmm1[0,1]
vroundps xmm0, xmm0, 9
vcvttps2dq xmm0, xmm0
vpshufd xmm1, xmm0, 78 # xmm1 = xmm0[2,3,0,1]
vpor xmm0, xmm0, xmm1
vpshufd xmm1, xmm0, 229 # xmm1 = xmm0[1,1,2,3]
vpor xmm0, xmm0, xmm1
vmovd eax, xmm0
cmp eax, 255
seta al
ret
我认为这种测试仍然可以使用英特尔 SIMD 指令进一步优化,但我不确定如何做到这一点。是否可以在纯 C++ 中更好地处理它,或者是否需要内在函数,甚至内联汇编程序?或者是否可以进一步优化?
要查看优化的输出,我使用 x86-64 clang 11.0.0,编译器标志为:-O3 -mtune=skylake -ffast-math -funsafe-math-optimizations -fno-math-errno -msse4.1 - mavx-mfma4
直接使用SIMD甚至看起来比原始代码更简单:
bool Check_If_Outside(std::array<float, 4> vec)
{
__m128 v = _mm_loadu_ps(vec.data());
__m128 tooHigh = _mm_cmpge_ps(v, _mm_set1_ps(256));
return _mm_movemask_ps(_mm_or_ps(v, tooHigh));
}
生成的代码(至少是我编译它的方式):
Check_If_Outside(std::array<float, 4ul>): # @Check_If_Outside(std::array<float, 4ul>)
vmovlhps xmm0, xmm0, xmm1 # xmm0 = xmm0[0],xmm1[0]
vbroadcastss xmm1, dword ptr [rip + .LCPI1_0] # xmm1 = [2.56E+2,2.56E+2,2.56E+2,2.56E+2]
vcmpleps xmm1, xmm1, xmm0
vorps xmm0, xmm0, xmm1
vmovmskps eax, xmm0
test eax, eax
setne al
ret
因此,我们跳过了整数转换,而是依靠
vmovmskps
,而不是进行水平或。
uiCA 认为此代码的倒数吞吐量(请注意,这不是延迟)为 6 个周期,而原始为 17 个周期(评论中链接的来自 Clang 15 的更优化版本,Skylake 的吞吐量为 13.47) ,Rocket Lake 为 15.13),所以看起来更好,但一定要真正尝试一下。