我是否可以在没有内联汇编的情况下使C ++生成cmpps指令?

时间:2016-02-29 06:33:14

标签: c++ sse

我希望现代C ++编译器能够生成最快的机器代码。或者我们还会在2016年坚持使用内联装配吗? 我需要搜索与一个特定框相交的浮点3D边界框。 我对汇编程序版本的想法是

1. cmpps => register A
2. cmpss => register B 
3. C = A & B
4. convert C into "normal" register
5. compare it with zero
6. conditionally jump

即使在第4个浮点数的填充结构之后,GCC 4.4和Visual Studio Comunity 2015也只使用comiss指令一次生成一个浮点数比较。是否需要部分C ++表达式顺序,或者这些编译器无法自行优化它?

我的测试用例:

struct Vec
{
    float x,y,z,w;
};

struct BBox
{
    Vec min,max;
};

bool bbox(BBox& b, Vec& v)
{
    return
        b.min.x <= v.x && v.x <= b.max.x &&
        b.min.y <= v.y && v.y <= b.max.y &&
        b.min.z <= v.z && v.z <= b.max.z &&
        b.min.w <= v.w && v.w <= b.max.w;
}

int main()
{
    BBox b;
    Vec v;
    return bbox(b, v);
}

2 个答案:

答案 0 :(得分:4)

太糟糕了,gcc / clang不会自动进行此操作,因为它是pretty easy

// clang doesn't let this be a constexpr to mark it as a pure function :/
const bool bbox(const BBox& b, const Vec& v)
{
    // if you can guarantee alignment, then these can be load_ps
    // saving a separate load instruction for SSE.  (AVX can fold unaligned loads)
    // maybe make Vec a union with __m128
    __m128 blo = _mm_loadu_ps(&b.min.x);
    __m128 bhi = _mm_loadu_ps(&b.max.x);
    __m128 vv  = _mm_loadu_ps(&v.x);

    blo = _mm_cmple_ps(blo, vv);
    bhi = _mm_cmple_ps(vv, bhi);
    __m128 anded = _mm_and_ps(blo, bhi);

    int mask = _mm_movemask_ps(anded);
    // mask away the result from the padding element,
    // check that all the bits are set
    return (mask & 0b0111) == 0b0111;
}

这将编译为

    movups  xmm0, xmmword ptr [rdi]
    movups  xmm1, xmmword ptr [rdi + 16]
    movups  xmm2, xmmword ptr [rsi]
    cmpleps xmm0, xmm2
    cmpleps xmm2, xmm1
    andps   xmm2, xmm0
    movmskps        eax, xmm2
    and     eax, 7
    cmp     eax, 7
    sete    al
    ret

如果你颠倒了比较感(cmpnle),要测试是否在任何轴上的边界框之外,你可以做类似的事情

int mask1 = _mm_movemask_ps(blo);
int mask2 = _mm_movemask_ps(bhi);
return !(mask1 | mask2);

可能编译为

movmsk
movmsk
or
setnz

所以整数测试更便宜,你用另一个movmsk替换一个向量AND(大约相等的成本)。

我想了一会儿这样做就意味着NaN算在盒子里面,但实际上当NaN中的一个操作数时cmpnleps是真的。 (在这种情况下,cmpleps是假的,所以它恰恰相反)。

我没有想过在这种情况下填充会发生什么。它可能最终成为!((mask1|mask2) & 0b0111),这对于x86来说仍然更有效,因为test指令可以免费执行AND,并且可以与Intel和AMD上的分支指令进行宏融合。

movmskps是AMD的2 m-ops和高延迟,但使用矢量可能仍然是一个胜利。 AMD上的两条movmskps指令可能比我先发布的代码略差,但它是流水线的,所以它们都可以在cmpps指令完成后进行传输。

答案 1 :(得分:1)

如果你真的想要CMPPS机器指令,use the __builtin_ia32_cmpps builtin

但我认为你可能不需要这样做。 信任您的编译器并要求使用gcc -ffast-math -mcpu=native -O3进行许多优化;也许添加一些其他optimization flags并考虑链接时优化,例如编译&amp;使用gcc -flto -ffast-math -mcpu=native -O3

链接

如果你有几个星期没有尝试手动调整优化(你的时间如此便宜,值得付出努力?),我建议通过小心巧妙地调整缓存预取/ em>添加few __builtin_prefetch(但是,基准与运行lasting more than a second并添加很少的预取!)。

手写和过早优化通常是无用的,适得其反

顺便说一句,编译器编写者正在做一些小而持续的进展。使用最新版本的GCCClang/LLVM进行测试可能是一种更好的方式来花时间。

不要忘记PC中timing of operations的典型数量级。