如何优化测试以检查 std::array<float, 4> 是否包含超出范围的值?

问题描述 投票:0回答:1

我有一个 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

c++ assembly optimization simd intrinsics
1个回答
0
投票

直接使用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),所以看起来更好,但一定要真正尝试一下。

© www.soinside.com 2019 - 2024. All rights reserved.