SSE比FPU慢吗?

9

我有一段大代码,其中的一部分包含以下代码:

result = (nx * m_Lx + ny * m_Ly + m_Lz) / sqrt(nx * nx + ny * ny + 1);

我已将其向量化,如下所示(所有内容均已转换为float):
__m128 r = _mm_mul_ps(_mm_set_ps(ny, nx, ny, nx),
                      _mm_set_ps(ny, nx, m_Ly, m_Lx));
__declspec(align(16)) int asInt[4] = {
    _mm_extract_ps(r,0), _mm_extract_ps(r,1),
    _mm_extract_ps(r,2), _mm_extract_ps(r,3)
};
float (&res)[4] = reinterpret_cast<float (&)[4]>(asInt);
result = (res[0] + res[1] + m_Lz) / sqrt(res[2] + res[3] + 1);

结果是正确的,但是我的基准测试显示向量化版本更慢
  • 非向量化版本需要3750毫秒
  • 向量化版本需要4050毫秒
  • 直接将result设置为0(并完全删除此部分代码)可以将整个过程缩短到2500毫秒
考虑到向量化版本仅包含一个 SSE乘法集合(而不是四个单独的FPU乘法),为什么它会更慢? FPU确实比SSE更快,还是存在混淆变量?
(我使用的是移动版Core i5。)

7
好久没在SO上看到SSE的问题了。我猜大家都从度假中回来了。 :) - Mysticial
3个回答

17
你花费了很多时间使用_mm_set_ps_mm_extract_ps将标量值从/到SSE寄存器中移动-这会生成很多指令,其执行时间远远超过使用_mm_mul_ps带来的任何好处。查看生成的汇编输出以查看除单个MULPS指令外还生成了多少代码。
要正确进行矢量化,您需要使用128位SSE加载和存储(_mm_load_ps / _mm_store_ps),然后使用SSE洗牌指令在必要时在寄存器内移动元素。
另一个要注意的点是,像Core i5、Core i7这样的现代CPU具有两个标量FPU,并且可以每个时钟发出2个浮点乘法。因此,对于单精度浮点数,使用SSE的潜在好处最多只有2倍。如果有过多的"管家"指令,就很容易失去这2倍的大部分或全部好处,就像这里所示的情况一样。

哎呀,没想到在寄存器之间移动数值如此缓慢!我实际上是想避免内存操作的。知道这点太好了,非常感谢! :) +1 - user541686
3
它之所以慢,是因为它在不同域(SSE-FP与通用寄存器)之间移动寄存器。跨域数据传输通常会额外增加1-2个周期的执行时间。 - Mysticial
@Mysticial:哦,好观点——我完全忘记了FPU寄存器与通用寄存器有很大的不同。 :) - user541686

4

以下是需要解决的问题:

  1. 在这种操作中,使用SSE指令不会带来太多好处,因为SSE指令应该在并行操作(即同时乘以几个值)上更有效。你所做的是对SSE的误用。
  2. 不要设置值,使用数组中第一个值的指针,但是你的值不在数组中。
  3. 不要提取和复制值到数组中,这也是对SSE的误用。结果应该在数组中。

0
我的看法是,当使用FPU加载下一个值时,处理器有时间计算第一次乘法。而SSE必须先加载所有值。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接