【发布时间】: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 及更早版本。
标签: c++ c++11 visual-studio-2013 sse