是的,我看过SIMD code runs slower than scalar code。不,它不是真的重复。
我一直在使用2D数学,并且在将代码库从C移植到C ++的过程中。我用C打了一些墙,这意味着我真的需要多态,但这是另一个故事。无论如何,我刚才考虑过这个,但它提供了一个使用2D矢量类的绝佳机会,包括常见数学运算的SSE实现。是的,我知道那里有图书馆,但我想亲自尝试一下,了解发生了什么,我不会使用比+=
更复杂的东西。
我的实施是通过<immintrin.h>
,带有
union {
__m128d ss;
struct {
double x;
double y;
}
}
SSE似乎很慢,所以我查看了它生成的ASM输出。在用指针修复一些愚蠢的东西之后,我最终得到了以下几组指令,在循环中运行了十亿次: (处理器是3.7 PhenGHz的AMD Phenom II)
启用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
快,而流水线意味着额外的指令可以通过更快速地部分重叠的能力来补偿。 / p>
所以我的问题是:这是否值得,在实践中它实际上会有所帮助,还是我不应该费心去做愚蠢的优化并让编译器以标量模式处理它?</ p>
答案 0 :(得分:7)
这需要更多的循环展开,并且可能需要缓存预取。您的算术密度非常低:1次操作用于2次内存操作,因此您需要尽可能多地将这些内容堵塞在管道中。
也不要直接使用union而是__m128d,并使用_mm_load_pd从数据中填充__m128。联合中的_m128生成错误的代码,其中所有元素都在进行堆栈寄存器堆栈跳舞,这是有害的。
答案 1 :(得分:2)
仅供记录,Agner Fog's instruction tables确认K10以相同的性能运行addpd
和addsd
:FADD单元为1 m-op,具有4个周期延迟。早期的K8只有64位执行单元,并将addpd
分成两个m-op。
因此两个循环都有一个4循环循环的依赖链。标量循环有两个独立的4c dep链,但仍然只能使FADD单位占用一半的时间(而不是1/4)。
管道的其他部分必须发挥作用,可能是代码对齐或只是指令排序。 AMD比英特尔,IIRC更敏感。我并不好奇地阅读K10管道并弄清楚Agner Fog的文档是否有解释。
K10没有将cmp / jcc融合到单个操作系统中,因此将它们分开并不是一个问题。 (Bulldozer家族的CPU,当然还有英特尔)。
答案 2 :(得分:1)
2D数学并不是处理器密集型(与3D数学相比)所以我非常怀疑它是否值得沉入其中。如果
,值得优化我已经在我的装备上进行了一些SSE测试(AMD APU @ 3GHz x 4;旧的Intel CPU @ 1.8Ghz x 2),并且发现SSE在我测试的大多数情况下都是有益的。但是,这适用于3D操作,而不是2D。
标量代码有更多的并行机会iirc。使用四个寄存器而不是两个;较少的依赖。如果寄存器争用变大,矢量化代码可能会运行得更好。尽管如此,我还没有考虑到这一点。