2013-02-14 66 views
2

阅读this interesting article on the results of intrinsic-guided optimization of SSE code in different C++ compilers我决定自己做一个测试,特别是因为这个帖子已经过了几年了。我使用了MSVC,它在帖子作者的测试中做得很差(虽然在VS 2010版本中),并决定坚持一个非常基本的场景:将一些值打包到XMM寄存器中,并进行简单操作,如添加。在文章中,_mm_set_ps翻译成标举的一个奇怪的序列,并解压缩指令,让我们看看:如何改进编译器对我的SSE内部函数的处理?

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 [富],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 [巴],XMM0
__m128 ret = _mm_add_ps(foo,bar);
00B81522 MOVAPS XMM0,xmmword的ptr [巴]
00B81526 MOVAPS xmm1中,xmmword的ptr [富]
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推ESI
003E1011 MOVAPS xmmword的ptr [ESP + 10H],XMM0

显然,不需要ADDPS。我猜测编译器注意到这两个xmmwords是编译时常量,所以它只是添加了它们,将结果作为文字输入到代码中?奇怪的推动可能与随后的for循环有关,因为esi被用作循环计数器,据我所知。尽管如此,为什么不将数据段中的预先计算的文字放入xmm0,然后放入局部变量(esp + 10h),为什么不直接使用文字?总而言之,Debug版本比我预期的更愚蠢(或者我可能没有收到什么东西),而发布版本却意想不到。任何意见解释这种行为将不胜感激。谢谢。

编辑:的答案是很有启发性,但我还是想知道如果有什么可以做些什么来改善编译器的输出,这就是为什么我从询问到的这种解释改变的问题目前的形式。

例如,才有可能以某种方式引导编译器酒吧不存储在内存中(因为我不添加后需要他们),只需将它们装入xmmN寄存器,并让他们那里?可能ret呢?引用文章的作者说,MSVC只是“按照它所告诉的那样做”。任何方式来改善(读:避免内存传输)代码,而不明确写一个__asm块?谢谢。

回答

4

这只是代码生成器工作方式的正常副作用。 _mm_set_ps()有两个不同的工作要做。它首先必须建立4个参数中的__m128值。你挑了最简单的方式,它得到了很多更令人费解:

float x = 1.0f; 
__m128 foo = _mm_set_ps(x, 2.0f, 3.0f, 4.0f); 

有了显着不同的代码生成:

00C513DD movss  xmm0,dword ptr ds:[0C5585Ch] 
00C513E5 movss  xmm1,dword ptr [x] 
00C513EA movaps  xmm2,xmmword ptr ds:[0C55860h] 
00C513F1 unpcklps xmm0,xmm1 
00C513F4 unpcklps xmm2,xmm0 
00C513F7 movaps  xmmword ptr [ebp-100h],xmm2 

第二个作业然后将其移动到__m128变量,这很容易

00C513FE movaps  xmm0,xmmword ptr [ebp-100h] 
00C51405 movaps  xmmword ptr [foo],xmm0 

这还没有优化,只是因为在Debug版本中关闭了优化器。代码生成器没有做任何优化的尝试,但这不是它的工作。

当然,优化器能够在编译时计算结果。这甚至适用于这个复杂的例子,你已经看到了这个:

00EE1284 movaps  xmm0,xmmword ptr ds:[0EE3260h] 
+0

一个非常好的解释! – us2012 2013-02-14 14:33:53

+0

谢谢@Hans,您是否也会好好处理Q上的编辑?谢谢。 – neuviemeporte 2013-02-14 15:18:43

+0

要做任何事情来改善它都没有意义。只需构建您的项目的发布版本即可完成。在调试版本中打开优化器只会让调试更加困难,调试版本的目的是让它更容易*。如果你真的想,你可以帮助你了解它有多难。 – 2013-02-14 15:27:41

1

你对发布版本的编译时优化是正确的(在你的目标文件中查找ds:[3E2130h],你会在那里找到附加的值)。

是,调试版本似乎做不必要的工作,但只有2倍,而不是一个4倍。有人会真正期待

movaps xmmword ptr [foo],xmmword ptr ds:[0B87840h] 

存在,但它没有,MOVAPS有两个变种,也不允许从内存移动到内存(这是x86的通常情况下):

MOVAPS xmm1,xmm2/mem128  ; 0F 28 /r  [KATMAI,SSE] 
MOVAPS xmm1/mem128,xmm2  ; 0F 29 /r  [KATMAI,SSE] 

什么调试组装并从ds:[0B87840h]在目标文件的.data部分阅读xmmword(这是米可能只读),并将其放在[ebp-190h]以及foo的堆栈中。

为了比较,GCC 4.7表现出类似的模式:

movaps xmm0, XMMWORD PTR .LC0[rip] # D.5374, 
movaps XMMWORD PTR [rbp-64], xmm0 # foo, D.5353 
movaps xmm0, XMMWORD PTR .LC1[rip] # D.5381, 
movaps XMMWORD PTR [rbp-48], xmm0 # bar, D.5354 
movaps xmm0, XMMWORD PTR [rbp-64] # tmp79, foo 
movaps XMMWORD PTR [rbp-32], xmm0 # __A, tmp79 
movaps xmm0, XMMWORD PTR [rbp-48] # tmp80, bar 
movaps XMMWORD PTR [rbp-16], xmm0 # __B, tmp80 
movaps xmm0, XMMWORD PTR [rbp-16] # tmp81, __B 
movaps xmm1, XMMWORD PTR [rbp-32] # tmp82, __A 
addps xmm0, xmm1 # D.5386, tmp82 

我会假设,这与该内置内在的实现方式做。例如,_mm_add_ps__m128参数一起使用,该参数可能在寄存器,堆栈中或调用时的其他地方。因此,如果您正在为gcc/VC++编写内部代码,则必须先生成将加载值的代码。优化程序运行时,会立即发现无需推送数据(但优化程序不会在调试版本中运行)。

+0

wrt。到“人们会期待'movaps [mem],[mem]'存在......” - 不。除了字符串移动('rep movs')以外,这种类型的指令(直接内存内存拷贝)在x86中不存在。 x86中的所有其他指令(包括所有SSE/AVX)只能拥有_a单一内存操作数。 – 2013-02-15 10:03:00

+0

@FrankH。这正是我所说的,如果你阅读:“(这是x86中的常见情况)”。尽管如此,如果你对汇编程序还不熟悉,期望有* [mem],[mem]指令并不是不合理的,但它是混淆的常见来源。 – us2012 2013-02-15 10:23:35

1

这真的是一个关于MSVC内部的问题。要得到明确答案,你必须问问微软。

有人可能会推测Release版本将ret放入局部变量的原因是您已经采用了它的地址。取一个变量的地址意味着编译器突然不得不处理内存而不是寄存器。内存对于编译器来说非常困难,因为程序中的其他地方可能有指向优化器必须考虑的指针。

相关问题