阅读了这篇有趣的文章,关于在不同的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;
}
}
接下来,我在调试器中编译并运行了这个程序,并查看了反汇编代码: 调试:
我感到非常困惑,为什么将xmmword放入__m128需要四个MOVAPS指令?首先,它将数据放入xmm0寄存器(我猜测它是存储四个浮点值的文字,存储在某个地方,但不确定如何查看),然后将xmm0复制到ebp和偏移量指向的某个位置,只为了再次从那里将其复制回xmm0(?),最后将其存储到变量的位置。为什么要这么麻烦呢? 发布: 这次我期望编译器根本不需要将xmmword存储在内存中,只需将一个存储在xmm0中,另一个存储在xmm1中,执行ADDPS操作,将结果存储在内存中,然后完成操作。但实际上,我得到了以下结果:__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 [foo],xmm0
__m128 bar = _mm_set_ps(5.0f, 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],xmm0
__m128 ret = _mm_add_ps(foo, bar);
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
__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);
在这段代码中,使用了SSE指令集的_mm_set_ps和_mm_add_ps函数进行浮点数运算。编译器可能会将常量编译成字面量,因此不需要使用ADDPS指令。push esi指令可能与后续循环有关,esi用作循环计数器。为什么要将预先计算好的字面量从数据段加载到xmm0寄存器,然后再放到本地变量(esp+10h)中,而不是直接使用字面量呢?Debug版本比预期更愚蠢,Release版本则出乎意料地聪明。如果有任何解释这种行为的评论,将不胜感激。如果想要改善编译器输出,是否有任何方法可以避免内存传输,例如将foo、bar和ret直接加载到xmmN寄存器中并保留在那里,而不是将它们存储在内存中?作者称MSVC只是“按照指令执行”,是否有方法可以获得更好的代码(即避免内存传输),而无需显式编写__asm块?