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

Posted

技术标签:

【中文标题】由 Visual Studio 2013 Update 2 和 Update 3 生成的 SSE 4 指令【英文标题】:SSE 4 instructions generated by Visual Studio 2013 Update 2 and Update 3 【发布时间】:2014-08-25 21:31:02 【问题描述】:

如果我在 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;

在启用 SSE2 指令但未启用 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];

其中包含pmaxsd 指令等内容。

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

Intel core2s 支持 sse3,但不支持 sse4,也不支持pmaxsd

这不会发生在 VS2013 更新 1 或更新 0 中。

有没有办法让 Visual Studio 生成 SSE2 指令而不是像 pmaxsd 这样的 SSE4 指令?这是 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;

Here is a link to a bug for this issue 大约在我发布此问题的同时,其他人打开了。

这是生成的.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

这里:

  cmp  esi, 8
  jb  SHORT $LN25@Code2
  cmp  DWORD PTR ___isa_available, 2
  jl  SHORT $LN25@Code2

如果 (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

没有 SSE 指令。然而,重要的部分是失败。如果eax(迭代参数)通过10,则落入:

; Line 28
  movd  xmm0, edx
  pshufd  xmm0, xmm0, 0
  pmaxsd  xmm1, xmm0

这是查找单步版本结果和 SSE4 结果的最大值的代码。第三条指令是pmaxsd,是SSE4_1指令,不受__isa_available保护。

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

请注意,我尝试删除循环的双重嵌套性质似乎使问题消失了。

【问题讨论】:

澄清:第一代 Core2 (65nm Merom/Conroe) 仅支持 SSSE3 及更早版本。第二代 Core2(45nm Penryn/Wolfdale)支持 SSE4.1 及更早版本。 Nehalem(第一个 Core-i7)支持 SSE4.2 及更早版本。 【参考方案1】:

这是documented behaviour:

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

如果您仔细查看编译器生成的代码,您会发现 SSE4.2 指令的使用取决于运行时测试:

cmp DWORD PTR ___isa_available, 2
jl  SHORT $LN11@Code

这里的值 2 apparently means 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)

作为documented on this bug report,/d2Qvec-sse2only 是一个未记录的标志,适用于更新 3(可能还有更新 2),以防止编译器输出 SSE4 指令。这可以自然地防止某些循环被矢量化。 /d2Qvec-sse2only 可能会在任何时候停止工作(它“可能会在未来更改,恕不另行通知”),可能在未来版本的 VC 上。

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

【讨论】:

我在反汇编中没有看到___isa_available。有cmp dword ptr ds:[33B324h],2jl Code+93h (0332E33h)——我应该使用不同的反汇编程序吗? 我在命令行编译器中使用了/Fa 选项让它生成一个汇编文件。 啊哈!当我做一些稍微复杂的事情(一个嵌套的最大值循环,带有内部动态数组)时,结果是有一些指令 not__isa_available 保护功能。如果您好奇的话,现在在我的问题中包括导致崩溃的更复杂的代码。 不幸的是,新示例中的崩溃是由代码中的缓冲区溢出引起的。您使用 new long[8] 为 8 个 long 分配空间,然后将 10 个 long 写入缓冲区。一旦修复了该错误,您的代码就可以在我的 Core 2 PC 上正常运行。 如果你想知道,设法戳 MS 足以从他们那里得到一个无证标志来解决问题 -- /d2Qvec-sse2only。介意我把它编辑成你的好答案,然后删除我的吗?

以上是关于由 Visual Studio 2013 Update 2 和 Update 3 生成的 SSE 4 指令的主要内容,如果未能解决你的问题,请参考以下文章

如何设置由 Visual Studio 2010 应用程序创建的文本文件的路径?

加载Visual Studio 2012和2015项目时出错

Visual Studio Express 2013 和 Visual Studio 2013 之间的区别

如何设置 TFS 2013 以使用 Visual Studio 2013 或 Visual Studio 2017 构建

在 Visual Studio 之外启动时程序运行速度较慢

如何使用Visual Studio 2013或Visual Studio 2017设置TFS 2013