SSE2双倍乘法比标准乘法慢

SSE2 double multiplication slower than with standard multiplication

本文关键字:标准 SSE2      更新时间:2023-10-16

我想知道为什么下面带有SSE2指令的代码执行乘法的速度比标准c++实现慢。下面是代码:

        m_win = (double*)_aligned_malloc(size*sizeof(double), 16);
        __m128d* pData = (__m128d*)input().data;
        __m128d* pWin = (__m128d*)m_win;
        __m128d* pOut = (__m128d*)m_output.data;
        __m128d tmp;
        int i=0;
        for(; i<m_size/2;i++)
            pOut[i] = _mm_mul_pd(pData[i], pWin[i]);

m_output.datainput().data分配了_aligned_malloc内存。

对于2^25数组执行此代码的时间与此代码(350ms)的时间相同:

for(int i=0;i<m_size;i++)
    m_output.data[i] = input().data[i] * m_win[i];

这怎么可能?理论上只需要50%的时间,对吧?或者是从SIMD寄存器到m_output的内存传输的开销。数据数组这么贵?

如果我替换第一个代码片段

中的行
pOut[i] = _mm_mul_pd(pData[i], pWin[i]);

tmp = _mm_mul_pd(pData[i], pWin[i]);

其中__m128d tmp;然后代码执行得非常快,低于我的计时器功能的分辨率。这是因为所有东西都存储在寄存器而不是内存中吗?

更令人惊讶的是,如果我在调试模式下编译,SSE代码只需要 93ms,而标准乘法需要309ms
  • 调试:93ms (SSE2)/309ms(标准乘法)
  • 释放:350ms (SSE2)/350(标准乘法)

这是怎么回事?

我在发布模式下使用MSVC2008和QtCreator 2.2.1。下面是RELEASE的编译器开关:

cl -c -nologo -Zm200 -Zc:wchar_t- -O2 -MD -GR -EHsc -W3 -w34100 -w34189

和这些是为调试:

cl -c -nologo -Zm200 -Zc:wchar_t- -Zi -MDd -GR -EHsc -W3 -w34100 -w34189

编辑关于RELEASE vs DEBUG问题:我只是想指出,我对代码进行了分析,SSE代码实际上在发布模式下更慢!这只是在某种程度上证实了VS2008在某种程度上不能正确处理优化器的内在的假设。Intel VTune在DEBUG模式下为SSE循环提供289ms,在RELEASE模式下为504ms。哇……只是哇…

首先,VS 2008对于intrisincs来说是一个糟糕的选择,因为它倾向于添加比必要更多的寄存器移动,并且通常不能很好地优化(例如,当存在SSE指令时,它存在循环归纳变量分析的问题)。

所以,我大胆的猜测是编译器生成mulss指令,CPU可以轻松地重新排序并并行执行(迭代之间没有依赖关系),而内在的结果是大量的寄存器移动/复杂的SSE代码——它甚至可能在现代CPU上破坏跟踪缓存。VS2008是臭名昭著的做所有它的计算寄存器,我猜会有一些危险,CPU不能跳过(像xor reg,移动mem->reg, xor, mov mem->reg, mul, mov mem->reg,这是一个依赖链,而标量代码可能是移动mem->reg, mul与mem操作数,mov。)你一定要看看生成的程序集,或者试试VS 2010,因为VS 2010有更好的对intrinsic的支持。

最后,也是最重要的:您的代码根本不是计算绑定的,任何SSE都不会使它显着更快。在每次迭代中,您读取四个双精度值并写入两个,这意味着FLOPs不是您的问题。在这种情况下,您将受缓存/内存子系统的支配,这可能解释了您看到的差异。调试乘法不应该比发布更快;如果你看到它比你快,你应该做更多的运行,并检查其他正在发生的事情(如果你的CPU支持turbo模式,要小心,这增加了另外20%的变化。)在这种情况下,清空缓存的上下文切换可能就足够了。

因此,总的来说,您所做的测试几乎没有意义,只是表明对于内存受限的情况,使用SSE或不使用SSE没有区别。如果确实存在计算密集且并行的代码,则应该使用SSE,即使这样,我也会花费大量时间使用分析器来确定要优化的确切位置。简单的点积不适合看到SSE的任何性能改进。

几点:

  • 已经指出,MSVC生成的SSE
  • 代码非常糟糕。你的代码几乎肯定是内存带宽有限的,因为你在加载和存储之间只执行一个操作。
  • 大多数现代x86 cpu都有两个浮点alu,因此使用SSE进行双精度浮点运算可能没有什么好处,即使您没有带宽限制