由Visual Studio 2013 Update 2和Update 3生成的SSE 4指令

Yak*_*ont 17 c++ sse c++11 visual-studio-2013

如果我在VS 2013 Update 2或Update 3中编译此代码:(以下来自Update 3)

#include "stdafx.h"
#include <iostream>
#include <random>

struct Buffer
{
  long* data;
  int   count;
};

#ifndef max
#define max(a,b)            (((a) > (b)) ? (a) : (b))
#endif

long Code(long* data, int count)
{
  long nMaxY = data[0];

  for (int nNode = 0; nNode < count; nNode++)
  {
    nMaxY = max(data[nNode], nMaxY);
  }

  return(nMaxY);
}

int _tmain(int argc, _TCHAR* argv[])
{
#ifdef __AVX__
  static_assert(false, "AVX should be disabled");
#endif
#ifdef __AVX2__
  static_assert(false, "AVX2 should be disabled");
#endif
  static_assert(_M_IX86_FP == 2, "SSE2 instructions should be enabled");
  Buffer buff;
  std::mt19937 engine;
  engine.seed(std::random_device{}());
  std::uniform_int_distribution<int> distribution(0, 100);

  buff.count = 1;
  buff.data = new long[1];
  buff.data[0] = distribution(engine);

  long result = Code(buff.data, buff.count);
  std::cout << result; // ensure result is used
  return result;
}
Run Code Online (Sandbox Code Playgroud)

启用S​​SE2指令但不启用AVX/AVX2,发行版中的编译器生成:

  {
    nMaxY = max(data[nNode], nMaxY);
010612E1  movdqu      xmm0,xmmword ptr [eax]  
010612E5  add         esi,8  
010612E8  lea         eax,[eax+20h]  
010612EB  pmaxsd      xmm1,xmm0  
010612F0  movdqu      xmm0,xmmword ptr [eax-10h]  
010612F5  pmaxsd      xmm2,xmm0  
010612FA  cmp         esi,ebx  
010612FC  jl          Code+41h (010612E1h)  
010612FE  pmaxsd      xmm1,xmm2  
01061303  movdqa      xmm0,xmm1  
01061307  psrldq      xmm0,8  
0106130C  pmaxsd      xmm1,xmm0  
01061311  movdqa      xmm0,xmm1  
01061315  psrldq      xmm0,4  
0106131A  pmaxsd      xmm1,xmm0  
0106131F  movd        eax,xmm1  
01061323  pop         ebx  
  long nMaxY = data[0];
Run Code Online (Sandbox Code Playgroud)

其中包含pmaxsd指令.

pmaxsd据我所知,指令是SSE4_1指令或AVX指令,而不是SSE2指令.

英特尔core2s支持sse3,但不支持sse4,而不支持sse4 pmaxsd.

在VS2013更新1或更新0中不会发生这种情况.

有没有办法让Visual Studio生成SSE2指令而不是SSE4指令pmaxsd?这是Visual Studio更新2/3中的已知错误吗?有解决方法吗?Visual Studio是否不再支持Core2处理器?


以下是上述代码的更复杂版本,它将(在默认版本设置下)编译为崩溃Core2 CPU的代码:

#include "stdafx.h"
#include <iostream>
#include <random>
#include <array>

enum unused_name {
  _nNumPolygons = 10,
};


#ifndef max
#define max(a,b)            (((a) > (b)) ? (a) : (b))
#endif

struct Buffer
{
  std::array<long*, _nNumPolygons> data;
  std::array<int, _nNumPolygons>   count;
};

long Code(Buffer* buff)
{
  long  nMaxY = buff->data[0][0];


  for (int nPoly = 0; nPoly < _nNumPolygons; nPoly++)
  {
    for (int nNode = 0; nNode < buff->count[nPoly]; nNode++)
    {
      nMaxY = max(buff->data[nPoly][nNode], nMaxY);
    }
  }

  return(nMaxY);
}

extern "C" __int32 __isa_available;

int _tmain(int argc, _TCHAR* argv[])
{
#ifdef __AVX__
  static_assert(false, "AVX should be disabled");
#endif
#ifdef __AVX2__
  static_assert(false, "AVX2 should be disabled");
#endif
#if !( defined( _M_AMD64 ) || defined( _M_X64 ) )
  static_assert(_M_IX86_FP == 2, "SSE2 instructions should be enabled");
#endif
  // __isa_available = 1; // to force code to act as if SSE4_2 is not available
  Buffer buff;
  std::mt19937 engine;
  engine.seed(std::random_device{}());
  std::uniform_int_distribution<int> distribution(0, 100);

  for (int i = 0; i < _nNumPolygons; ++i) {
    buff.count[i] = 10;
    buff.data[i] = new long[10];
    for (int k = 0; k < 10; ++k)
    {
      buff.data[i][k] = distribution(engine);
    }
  }

  long result = Code(&buff);
  std::cout << result; // ensure result is used
  return result;
}
Run Code Online (Sandbox Code Playgroud)

这是一个链接到这个问题的错误,其他人在我发布这个问题的同时打开了这个问题.

这是生成的.asm:

?Code2@@YAJPAUBuffer@@@Z PROC        ; Code2, COMDAT
; _buff$ = ecx
; File c:\users\adam.nevraumont.corelcorp.000\documents\visual studio 2013\projects\consoleapplication1\consoleapplication1\consoleapplication1.cpp
; Line 22
  push  ebp
  mov  ebp, esp
  sub  esp, 12          ; 0000000cH
  push  ebx
  push  esi
  push  edi
  mov  edi, ecx
; Line 26
  xor  ebx, ebx
  mov  DWORD PTR _buff$1$[ebp], edi
  mov  DWORD PTR _nPoly$1$[ebp], ebx
  mov  eax, DWORD PTR [edi]
  mov  edx, DWORD PTR [eax]
; Line 28
  movd  xmm0, edx
  pshufd  xmm1, xmm0, 0
  movdqa  xmm2, xmm1
  npad  12
$LL6@Code2:
  lea  ecx, DWORD PTR [ebx*4]
  xor  eax, eax
  mov  esi, DWORD PTR [ecx+edi+40]
  mov  DWORD PTR tv443[ebp], ecx
  test  esi, esi
  jle  SHORT $LN5@Code2
  cmp  esi, 8
  jb  SHORT $LN25@Code2
  cmp  DWORD PTR ___isa_available, 2
  jl  SHORT $LN25@Code2
; Line 26
  mov  ebx, DWORD PTR [ecx+edi]
  mov  ecx, esi
  and  ecx, -2147483641      ; 80000007H
  jns  SHORT $LN33@Code2
  dec  ecx
  or  ecx, -8          ; fffffff8H
  inc  ecx
$LN33@Code2:
  mov  edi, esi
  sub  edi, ecx
  npad  8
$LL3@Code2:
; Line 30
  movdqu  xmm0, XMMWORD PTR [ebx+eax*4]
  pmaxsd  xmm1, xmm0
  movdqu  xmm0, XMMWORD PTR [ebx+eax*4+16]
  add  eax, 8
  pmaxsd  xmm2, xmm0
  cmp  eax, edi
  jl  SHORT $LL3@Code2
  mov  ebx, DWORD PTR _nPoly$1$[ebp]
  mov  ecx, DWORD PTR tv443[ebp]
  mov  edi, DWORD PTR _buff$1$[ebp]
$LN25@Code2:
; Line 28
  cmp  eax, esi
  jge  SHORT $LN5@Code2
; Line 26
  mov  edi, DWORD PTR [ecx+edi]
  npad  4
$LL23@Code2:
; Line 30
  cmp  DWORD PTR [edi+eax*4], edx
  cmovg  edx, DWORD PTR [edi+eax*4]
  inc  eax
  cmp  eax, esi
  jl  SHORT $LL23@Code2
$LN5@Code2:
; Line 26
  mov  edi, DWORD PTR _buff$1$[ebp]
  inc  ebx
  mov  DWORD PTR _nPoly$1$[ebp], ebx
  cmp  ebx, 10          ; 0000000aH
  jl  $LL6@Code2
; Line 28
  movd  xmm0, edx
  pshufd  xmm0, xmm0, 0
  pmaxsd  xmm1, xmm0
  pmaxsd  xmm1, xmm2
  movdqa  xmm0, xmm1
  psrldq  xmm0, 8
  pmaxsd  xmm1, xmm0
  movdqa  xmm0, xmm1
  pop  edi
  psrldq  xmm0, 4
  pmaxsd  xmm1, xmm0
  pop  esi
  movd  eax, xmm1
  pop  ebx
; Line 35
  mov  esp, ebp
  pop  ebp
  ret  0
Run Code Online (Sandbox Code Playgroud)

这里:

  cmp  esi, 8
  jb  SHORT $LN25@Code2
  cmp  DWORD PTR ___isa_available, 2
  jl  SHORT $LN25@Code2
Run Code Online (Sandbox Code Playgroud)

如果(A)循环小于8长,或者(B)我们没有SSE3/SSE4支持,我们有分支到"单步"版本的测试.

单步版本是:

$LN5@Code2:
; Line 26
  mov  edi, DWORD PTR _buff$1$[ebp]
  inc  ebx
  mov  DWORD PTR _nPoly$1$[ebp], ebx
  cmp  ebx, 10          ; 0000000aH
  jl  $LL6@Code2
Run Code Online (Sandbox Code Playgroud)

没有SSE指令.然而,重要的部分是堕落.如果eax(迭代参数)通过10,它会进入:

; Line 28
  movd  xmm0, edx
  pshufd  xmm0, xmm0, 0
  pmaxsd  xmm1, xmm0
Run Code Online (Sandbox Code Playgroud)

这是找到单步版本结果和SSE4结果的最大值的代码.第3条指令是pmaxsdSSE4_1指令,不受保护__isa_available.

是否有编译器设置或解决方法可以保持自动矢量化不变,而不在启用Core2 SSE2的计算机上调用SSE4_1指令?我的代码中是否存在导致此问题发生的错误?

请注意,我尝试删除循环的双嵌套特性似乎会使问题消失.

Ros*_*dge 15

这是记录在案的行为:

如果您的计算机支持,Auto-Vectorizer也会使用较新的SSE4.2指令集.

如果仔细观察编译器生成的代码,您会发现SSE4.2指令的使用依赖于运行时测试:

cmp DWORD PTR ___isa_available, 2
jl  SHORT $LN11@Code
Run Code Online (Sandbox Code Playgroud)

这里的值2 显然意味着SSE4.2.

然而,我能够在第二个例子中确认错误.事实证明我使用的Core 2 PC支持SSE4.1和PMAXSD指令,所以我不得不在带有Pentium 4 CPU的PC上测试它以获得非法指令异常.您应该向Microsoft Connect提交错误报告.请务必提及您的示例代码失败的特定Core 2 CPU型号.

至于解决方法,我只能建议更改受影响功能的优化级别.从优化速度切换到优化尺寸似乎产生与仅使用SSE2指令大致相同的代码.您可以使用#pragma optimize这样切换优化级别:

#pragma optimize("s", on)

long Code(Buffer* buff)
{
     ...
}

#pragma optimize("", on)
Run Code Online (Sandbox Code Playgroud)

如此错误报告/d2Qvec-sse2only所记录的那样,是一个未记录的标志,可用于更新3(可能还有更新2),以防止编译器输出SSE4指令.这可以自然地防止某些循环被矢量化. /d2Qvec-sse2only可能在VC的未来版本中,可能在任何时候停止工作(它"将在未经通知的情况下进行更改").

Microsoft声称此问题已在Update 4和Update 4 CTP 2中修复(不适用于生产用途).

  • 如果你想知道,设法捅MS足以从他们那里得到一个未记录的标志来禁用问题 - "/ d2Qvec-sse2only`.介意我把它编辑成你的答案,并删除我的? (3认同)