如何改进编译器对我的SSE内在函数的处理?

在阅读了关于不同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 [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

完全困惑; 为什么将xmmword放入__m128需要四个MOVAPS? 首先,它将数据放入xmm0中(我认为这是四个浮点值存储在某处的文字,不知道如何查看它),然后将xmm0复制到某处由ebp指向的位置和一个偏移量,仅将其复制回来到xmm0(?),最后到应该存储变量的位置。 为什么这么多工作?

发布:这次我期待编译器避免将xmmword存储在内存中,只需将xmm0放在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版本比我预期的要更愚蠢(或者我可能没有收到什么东西),而发布版本却意想不到。 任何意见解释这种行为将不胜感激。 谢谢。

编辑:答案是非常有启发性的,但我仍然想知道是否有任何可以做的改进编译器输出,这就是为什么我改变问题从要求解释这到当前的形式。

例如,是否有可能以某种方式指导编译器不将foo和bar存储在内存中(因为在添加之后我不需要它们),只需将它们加载到xmmN寄存器并将它们保存在那里? 可能也回收? 引用文章的作者说,MSVC只是“按照它所告诉的那样做”。 任何方式来改善(读:避免内存传输)代码,而不明确写一个__asm块? 谢谢。


这只是代码生成器工作方式的常见副作用。 _mm_set_ps()有两个不同的工作要做。 它首先必须建立4个参数中的__m128值。 你选择了简单的方式,它变得更加复杂:

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

随着codegen的截然不同:

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]  

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

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


您对版本构建的编译时优化是正确的(在对象文件中查找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]

调试程序集所做的是从目标文件的.data节中的ds:[0B87840h]中读取xmmword(很可能是只读的),并将它放在[ebp-190h]以及foo的堆栈中。

为了比较,海湾合作委员会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参数一起使用, __m128参数可能在寄存器,堆栈中或调用时的其他地方。 因此,如果您正在为gcc / VC ++编写内部代码,则必须先生成将加载值的代码。 优化程序运行时,会立即发现无需推送数据(但优化程序不会在调试版本中运行)。

链接地址: http://www.djcxy.com/p/86787.html

上一篇: How can I improve the compiler's handling of my SSE intrinsics?

下一篇: How to Make a Call to Koa.js App Instance for Unit Tests