4

是的,我读到SIMD 代码运行速度比标量代码慢。不,它不是真正的复制品。

我一直在使用 2D 数学的东西,并且在将我的代码库从 C 移植到 C++ 的过程中。我用 C 碰到了几堵墙,这意味着我真的需要多态性,但那是另一回事了。无论如何,我不久前考虑过这个问题,但它提供了使用 2D 矢量类的绝佳机会,包括常见数学运算的 SSE 实现。是的,我知道那里有库,但我想自己尝试一下以了解发生了什么,而且我不使用任何比+=.

我的实现是 via <immintrin.h>,带有

union {
    __m128d ss;
    struct {
        double x;
        double y;
    }
}

SSE 似乎很慢,所以我查看了它生成的 ASM 输出。在修复了一些愚蠢的指针后,我最终得到了以下指令集,循环运行了十亿次:(处理器是 AMD Phenom II,频率为 3.7GHz)

启用 SSE:1.1 到 1.8 秒(可变)

add      $0x1, %eax
addpd    %xmm0, %xmm1
cmp      $0x3b9aca00, %eax
jne      4006c8

SSE 禁用:1.0 秒(相当恒定)

add      $0x1, %eax
addsd    %xmm0, %xmm3
cmp      $0x3b9aca00, %eax
addsd    %xmm2, %xmm1
jne      400630

我可以从中得出的唯一结论是,addsd它比 快addpd,并且流水线意味着额外的指令可以通过执行更多更快的部分重叠的事情的能力来补偿。

所以我的问题是:这值得吗,在实践中它真的有帮助吗,还是我不应该为愚蠢的优化而烦恼,让编译器在标量模式下处理它?

4

3 回答 3

7

这需要更多的循环展开,并且可能需要缓存预取。您的算术密度非常低:2 个内存操作的 1 个操作,因此您需要在管道中尽可能多地堵塞这些操作。

也不要直接使用 union 而 __m128d 并使用 _mm_load_pd 从数据中填充 __m128。union 中的 _m128 生成错误代码,其中所有元素都在进行堆栈-寄存器-堆栈舞蹈,这是有害的。

于 2012-06-21T20:43:06.743 回答
2

仅作记录,Agner Fog 的指令表确认 K10 运行addpdaddsd具有相同的性能:FADD 单元为 1 m-op,具有 4 个周期延迟。早期的 K8 确实只有 64 位执行单元,并分为addpd两个 m-ops。

所以两个循环都有一个 4 循环循环携带的依赖链。标量循环有两个独立的 4c dep 链,但这仍然只使 FADD 单元占用一半时间(而不是 1/4)。

管道的其他部分必须发挥作用,可能是代码对齐或只是指令排序。AMD 对此比 Intel、IIRC 更敏感。我对阅读 K10 管道并弄清楚 Agner Fog 的文档中是否有解释感到好奇。

K10 不会将 cmp/jcc 融合到单个操作中,因此将它们拆分实际上不是问题。(推土机系列 CPU 可以,当然英特尔也可以)。

于 2015-12-24T12:21:02.367 回答
1

2D 数学不是处理器密集型(与 3D 数学相比),所以我非常怀疑是否值得花这么多时间。值得优化如果

  1. 您的分析器说代码是热点。
  2. 您的代码运行缓慢。(我想这是为了游戏?)
  3. 您已经优化了高级算法。

我在我的设备(AMD APU @ 3GHz x 4;旧的 Intel CPU @ 1.8Ghz x 2)上进行了一些 SSE 测试,发现 SSE 在我测试过的大多数情况下都有好处。但是,这是针对 3D 操作,而不是 2D。

标量代码具有更多并行 iirc 的机会。使用四个寄存器而不是两个;更少的依赖。如果寄存器争用变得更大,矢量化代码可能会运行得更好。不过,对此持保留态度,我还没有对此进行测试。

于 2012-06-21T20:21:58.507 回答