SSE2双倍乘法比标准乘法慢
SSE2 double multiplication slower than with standard multiplication
我想知道为什么下面带有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.data
和input().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;
然后代码执行得非常快,低于我的计时器功能的分辨率。这是因为所有东西都存储在寄存器而不是内存中吗?
- 调试: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指令时,它存在循环归纳变量分析的问题)。
所以,我大胆的猜测是编译器生成 最后,也是最重要的:您的代码根本不是计算绑定的,任何SSE都不会使它显着更快。在每次迭代中,您读取四个双精度值并写入两个,这意味着FLOPs不是您的问题。在这种情况下,您将受缓存/内存子系统的支配,这可能解释了您看到的差异。调试乘法不应该比发布更快;如果你看到它比你快,你应该做更多的运行,并检查其他正在发生的事情(如果你的CPU支持turbo模式,要小心,这增加了另外20%的变化。)在这种情况下,清空缓存的上下文切换可能就足够了。 因此,总的来说,您所做的测试几乎没有意义,只是表明对于内存受限的情况,使用SSE或不使用SSE没有区别。如果确实存在计算密集且并行的代码,则应该使用SSE,即使这样,我也会花费大量时间使用分析器来确定要优化的确切位置。简单的点积不适合看到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的支持。
几点:
- 已经指出,MSVC生成的SSE 代码非常糟糕。你的代码几乎肯定是内存带宽有限的,因为你在加载和存储之间只执行一个操作。
- 大多数现代x86 cpu都有两个浮点alu,因此使用SSE进行双精度浮点运算可能没有什么好处,即使您没有带宽限制
- 使用CMake检测支持的C++标准
- 如何理解C++标准N3337中的expr.const.cast子句8
- "throw expression code" 1e7 >返回 d 是什么?投掷标准::overflow_error( "too big" ) : d;意味 着?
- 编译标准库类型
- 标准是否使用多余的大括号(例如 T{{{10}}})定义列表初始化?
- 编译器如何在使用SFINAE的函数和标准函数之间确定两者是否可行
- 铸造标准::有没有回到原来的类型
- 标准 N3337 5.2.10 第 7 条中的C++"类型"是什么意思?
- this_thread::sleep_for和计时时钟之间的关系是否由C++11标准指定
- 标准库类型的赋值运算符的引用限定符
- 标准是否严格定义了该程序应该如何编译?
- 如何从Windows应用程序输出到标准?
- 安全到标准:移动会员?
- 如何正确将字符串转换为标准::时间::system_clock::time_point?
- 这是否符合C++标准:双响双响,例如!!(-0.0).
- 标准::变体的赋值运算符
- 捕获标准输出以压缩并使用 CTRL-C 中断会给出损坏的 zip 文件
- 如何在 Mac 上使用 c++17 并行标准库算法?
- 强枚举类型定义:Clang Bug 还是 C++11 标准不确定性?
- SSE2双倍乘法比标准乘法慢