我想知道为什么以下带有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.data数组的内存传输开销如此昂贵?
如果我替换第一个代码段
中的行pOut[i] = _mm_mul_pd(pData[i], pWin[i]);
通过
tmp = _mm_mul_pd(pData[i], pWin[i]);
其中__m128d tmp;
然后代码执行速度极快,低于我的计时器功能的分辨率。
那是因为一切都只是存储在寄存器而不是存储器中吗?
更令人惊讶的是,如果我在调试模式下编译,SSE代码需要仅93ms ,而标准乘法需要 309ms 。
这里发生了什么???
我在发布模式下使用MSVC2008和QtCreator 2.2.1。 以下是我的RELEASE编译开关:
cl -c -nologo -Zm200 -Zc:wchar_t- -O2 -MD -GR -EHsc -W3 -w34100 -w34189
这些是DEBUG:
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。 哇......哇哇......
答案 0 :(得分:3)
首先,VS 2008对于inisincs来说是一个糟糕的选择,因为它往往会增加比必要更多的寄存器移动,并且通常不能很好地进行优化(例如,当SSE指令是SSE指令时,它存在环路归纳变量分析的问题本。)
所以,我的猜测是编译器生成mulss
指令,CPU可以简单地重新排序并执行并行执行(迭代之间没有依赖关系),而intrisincs导致大量的寄存器移动/复杂的SSE代码 - - 它甚至可能会破坏现代CPU上的跟踪缓存。 VS2008因在寄存器中进行所有计算而臭名昭着,我想CPU会出现一些危险(例如xor reg,移动mem-&gt; reg,xor,mov mem-&gt; reg,mul,mov mem-&gt ; reg是一个依赖链,而标量代码可能是移动mem-&gt; reg,mul和mem操作数,mov。)你一定要查看生成的程序集或尝试VS 2010,其中很多更好地支持intrinsincs。
最后,也是最重要的:你的代码不是计算绑定的 ,没有任何数量的SSE会使它明显加快。在每次迭代中,您正在读取四个双值并写入两个,这意味着FLOP不是您的问题。在这种情况下,你受到缓存/内存子系统的支配,这可能解释了你看到的差异。调试乘法不应该比释放快;如果你看到它比你应该做更多的运行更快并检查还有什么进展(小心如果你的CPU支持turbo模式,那就会增加另外20%的变化。)一个清空缓存的上下文切换可能就足够了这种情况。
所以,总的来说,你所做的测试几乎毫无意义,只是表明对于内存绑定的情况,使用SSE没有区别。如果实际上存在计算密集且并行的代码,则应该使用SSE,即使这样,我也会花很多时间使用分析器来确定优化的确切位置。一个简单的点产品不适合用SSE看到任何性能改进。
答案 1 :(得分:1)
有几点: