在阅读了这篇关于在不同 C++ 编译器中对 SSE 代码进行内在引导优化的结果的有趣文章后,我决定自己做一个测试,特别是因为这篇文章已经有几年了。我使用了 MSVC,它在帖子作者执行的测试中表现非常糟糕(尽管在 VS 2010 版本中)并决定坚持一个非常基本的场景:将一些值打包到 XMM 寄存器中并执行简单的操作,如加法. 在文章中,_mm_set_ps 翻译成一个奇怪的标量移动和解包指令序列,所以让我们看看:
int _tmain(int argc, _TCHAR* argv[])
{
__m128 foo = _mm_set_ps(1.0f, 2.0f, 3.0f, 4.0f);
__m128 bar = _mm_set_ps(5.0f, 6.0f, 7.0f, 8.0f);
__m128 ret = _mm_add_ps(foo, bar);
// need to do something so vars won't be optimized out in Release
float *f = (float *)(&ret);
for (int i = 0; i < 4; i++)
{
cout << "f[" << i << "] = " << f[i] << endl;
}
}
接下来,我在调试器中编译并运行它,查看反汇编:
调试:
__m128 foo = _mm_set_ps(1.0f, 2.0f, 3.0f, 4.0f);
00B814F0 movaps xmm0,xmmword ptr ds:[0B87840h]
00B814F7 movaps xmmword ptr [ebp-190h],xmm0
00B814FE movaps xmm0,xmmword ptr [ebp-190h]
00B81505 movaps xmmword ptr [08],xmm0
_psm.2 6.0f、7.0f、8.0f);
00B81509 movaps xmm0,xmmword ptr ds:[0B87850h]
00B81510 movaps xmmword ptr [ebp-170h],xmm0
00B81517 movaps xmm0,xmmword ptr [ebp-170h]
00B8151E movaps xmmword ptr,bar],_
mm_add_ps1( );
00B81522 movaps xmm0,xmmword ptr [bar]
00B81526 movaps xmm1,xmmword ptr [foo]
00B8152A addps xmm1,xmm0
00B8152D movaps xmmword ptr [ebp-150h],xmm1
00B81534 movaps xmm0,xmmword ptr [ebp-150h]
00B8153B movaps xmmword ptr [ret],xmm0
完全糊涂;为什么将 xmmword 放入 __m128 需要四个 MOVAPS?首先,它将数据放入 xmm0 (我假设它是存储在某处的四个浮点值的文字,不知道如何查看它),然后将 xmm0 复制到 ebp 指向的某处和一个偏移量,然后再将其复制回来到 xmm0 (?),最后到应该存储它的变量的位置。为什么要做这么多工作?
发布: 这次我希望编译器完全避免将 xmmword 存储在内存中,只需将一个放在 xmm0 中,另一个放在 xmm1 中,执行 ADDPS,将结果放在内存中并完成它。相反,我得到了:
__m128 foo = _mm_set_ps(1.0f, 2.0f, 3.0f, 4.0f);
__m128 bar = _mm_set_ps(5.0f, 6.0f, 7.0f, 8.0f);
__m128 ret = _mm_add_ps(foo, bar);
003E1009 movaps xmm0,xmmword ptr ds:[3E2130h]
003E1010 push esi
003E1011 movaps xmmword ptr [esp+10h],xmm0
显然,不需要 ADDPS。我猜编译器注意到这两个 xmmwords 是编译时常量,所以它只是添加了它们,将结果作为文字放入代码中?奇怪的推动可能与随后的 for 循环有关,因为据我所知, esi 在那里用作循环计数器。不过,为什么要将数据段中预先计算的文字放入 xmm0 中,然后放入局部变量(esp+10h),为什么不直接使用文字呢?
总而言之,Debug 版本比我预期的更愚蠢(或者我可能没有得到什么),而 Release 版本却出人意料地聪明。任何解释此行为的评论将不胜感激。谢谢。
编辑:答案非常有启发性,但我仍然想知道我是否可以做些什么来改进编译器输出,这就是为什么我将问题从要求对此进行解释更改为当前形式。
例如,是否有可能以某种方式引导编译器不将foo和bar存储在内存中(因为添加后我不需要它们),只需将它们加载到 xmmN 寄存器并将它们保留在那里?也有可能退吗?引用文章的作者说,MSVC 只是“完全按照它的指示行事”。有什么方法可以在不显式编写 __asm 块的情况下变得更好(阅读:避免内存传输)代码?谢谢。