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

gojuced7  于 5个月前  发布在  其他
关注(0)|答案(2)|浏览(67)

我有一个4D vector:std::array<float,4>
我想检查它的所有组件是否都在值范围内:0.0f <= X && X < 256.0f
我如何检查是否有任何向量分量在这个范围之外?我只需要一个bool就可以知道整个向量是否通过测试。
我第一次尝试解决这个问题是以下代码:

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 -mfma 4
编辑:问题在你的帮助下解决了!谢谢!

gajydyqb

gajydyqb1#

直接使用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
另一个想法,基于通过原始位模式比较浮点数,

bool Check_If_Outside(std::array<float, 4> vec)
{
    __m128i v = _mm_loadu_si128((__m128i*)vec.data());
    __m128i bits256 = _mm_set1_epi32(0x43800000);
    __m128i cmp = _mm_cmpeq_epi32(_mm_min_epu32(v, bits256), bits256);
    return _mm_movemask_ps(_mm_castsi128_ps(cmp));
}


无论哪种方式,我都公然忽略了负零和NaN的存在,但-ffast-math已经忽略了NaN。
对于Skylake,如果我假设输入来自内存(而不是将它们从寄存器中混洗在一起-以打破任何偶然的依赖性),则会得到uiCA结果:

  • Original:3.39
  • 版本1,浮点比较:2.0
  • 版本2,整数比较:2.0
  • this来自评论:3.21
  • this来自评论:3.00(启用AVX 2时会变得更糟)

在所有情况下,这只是一些粗略的指示,这里的代码是单独分析的,而不是在任何真实的上下文中,但是吞吐量是依赖于上下文的(其影响例如执行端口压力)。此外,setcc在大多数真实的用法中应该消失(其中此函数内联到调用者中,条件立即分支)。(或者从内存中加载输入,我用它来做uiCA分析)将被任何实际产生输入的东西所取代。

5m1hhzi4

5m1hhzi42#

如果您接受-0.0f被视为外部,则可以利用仅为abs(x)>=2.0f(或NaNs)设置指数的最高位,并为负输入(包括(-0.0f))设置符号位。
因此,在将向量缩放1.f/128.f之后,您可以检查是否有任何元素具有最高位集,这可以通过ptest针对具有高位集的掩码来完成。

bool Check_If_Outside(std::array<float, 4> const & vec)
{
    __m128 v = _mm_loadu_ps(vec.data());
    __m128 v_scaled = _mm_mul_ps(v, _mm_set1_ps(1.f/128.f));
    __m128i mask = _mm_set1_epi32(0xc0000000);
    return !_mm_testz_si128(_mm_castps_si128(v_scaled), mask);
}

字符串

相关问题