【问题标题】:Can I make C++ generate cmpps instruction without inline assembly?我可以在没有内联汇编的情况下让 C++ 生成 cmpps 指令吗?
【发布时间】:2016-02-29 17:28:59
【问题描述】:

我希望现代 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);
}

【问题讨论】:

  • 当前 GCC 为 2 月的 GCC 5.3。 2016. 你如何调用它?你试过gcc -ffast-math -O3 -mcpu=native 吗?
  • 顺便说一句,你为什么要关心一些特定的机器代码......重要的是性能......只有gcc -S没有优化。如果您想查看生成的汇编代码,请尝试gcc -S -fverbose-asm -ffast-math -mcpu=native -O3
  • 多年来一直不需要内联汇编。您可以使用intrinsics 编写可移植到所有主要x86 编译器的代码。对于cmpltpsandpsmovmaskps,这看起来是一个很好的案例,然后测试整数结果以确保它已设置所有三个位。 (屏蔽用于比较填充的位,并测试== 0b111)。不幸的是,看起来这个单一功能不会自动矢量化。它可能在一个循环中,但仍然可能不是。
  • @MikeMB:我什至没有看main() 的代码。我查看了bbox() 的代码。永远不要使用main 来查看 gcc 是如何编译的:它会将其标记为“冷”并且优化较少。只需编写接受输入参数并返回结果的函数。

标签: c++ sse


【解决方案1】:

太糟糕了 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

因此整数测试更便宜,并且您将向量 AND 替换为另一个 movmsk(大约相同的成本)。

我想了一会儿,这样做意味着将 NaN 计为框内,但实际上当 NaN 中的操作数之一时 cmpnleps 为真。 (在这种情况下 cmpleps 是错误的,所以它确实是相反的)。

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

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

【讨论】:

    【解决方案2】:

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

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

    如果您有数周的时间来尝试手动优化优化(您的时间如此便宜以至于值得付出努力吗?),我建议您调整缓存预取,仔细巧妙 添加一个 few __builtin_prefetch (但是,仔细地进行基准测试运行 lasting more than a second 并添加很少的预取!)。

    手写和过早的优化往往是无用的,而且适得其反

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

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

    【讨论】:

      猜你喜欢
      • 2012-05-05
      • 2019-08-09
      • 2017-10-30
      • 1970-01-01
      • 2020-11-11
      • 2014-07-04
      • 2017-09-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多