c++ - Instrucciones SSE 4 generadas por Visual Studio 2013 Update 2 y Update 3
c++11 visual-studio-2013 (1)
Este es un comportamiento documentado :
El Auto-Vectorizador también usa el nuevo conjunto de instrucciones SSE4.2 si su computadora lo admite.
Si observa más de cerca el código que genera el compilador, verá que el uso de las instrucciones SSE4.2 depende de una prueba de tiempo de ejecución:
cmp DWORD PTR ___isa_available, 2
jl SHORT $LN11@Code
El valor 2 aquí aparentemente significa SSE4.2 .
Sin embargo, pude confirmar el error en tu segundo ejemplo. Resulta que la PC Core 2 que estaba usando es compatible con SSE4.1 y la instrucción PMAXSD
, así que tuve que probarlo en una PC con una CPU Pentium 4 para obtener la excepción de instrucción ilegal. Debe enviar un informe de error a Microsoft Connect . Asegúrese de mencionar el modelo de CPU Core 2 específico en el que falla el código de ejemplo.
En cuanto a una solución alternativa, solo puedo sugerir cambiar el nivel de optimización para la función afectada. El cambio de la optimización de la velocidad a la optimización del tamaño parece generar casi el mismo código que se usaría solo con las instrucciones del SSE2. Puede usar #pragma optimize
para cambiar el nivel de optimización de esta manera:
#pragma optimize("s", on)
long Code(Buffer* buff)
{
...
}
#pragma optimize("", on)
Como se documenta en este informe de errores , /d2Qvec-sse2only
es un indicador no documentado que funciona en la actualización 3 (y posiblemente en la actualización 2) para evitar que el compilador envíe instrucciones SSE4. Esto puede evitar que algunos bucles se vectoricen, naturalmente. /d2Qvec-sse2only
puede dejar de funcionar en cualquier momento (está "sujeto a cambios futuros sin previo aviso"), posiblemente en versiones futuras de VC.
Microsoft afirma que este problema se solucionó en la Actualización 4 y en la Actualización 4 CTP 2 (no para uso de producción).
Si compilo este código en VS 2013 Update 2 o Update 3: (a continuación viene de 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;
}
con las instrucciones SSE2 habilitadas, pero no AVX / AVX2, el compilador en versión genera:
{
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];
que contiene, entre otras cosas, instrucciones pmaxsd
.
pmaxsd
instrucciones pmaxsd
son instrucciones SSE4_1 o instrucciones AVX, por lo que puedo decir, no instrucciones SSE2.
Intel core2s admite sse3, pero no sse4, y no pmaxsd
.
Esto no ocurre en la actualización 1 o la actualización 0 de VS2013.
¿Hay alguna manera de que Visual Studio genere instrucciones SSE2 pero no instrucciones SSE4 como pmaxsd
? ¿Es este un error conocido en la actualización 2/3 de Visual Studio? ¿Hay una solución? ¿Visual Studio ya no es compatible con los procesadores Core2?
Aquí hay una versión más compleja del código anterior que compila (bajo la configuración predeterminada de la versión) el código que bloquea una CPU Core2:
#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;
}
Aquí hay un enlace a un error para este problema que alguien más abrió al mismo tiempo que publiqué esta pregunta.
Aquí está el .asm generado:
?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
Aquí:
cmp esi, 8
jb SHORT $LN25@Code2
cmp DWORD PTR ___isa_available, 2
jl SHORT $LN25@Code2
tenemos la prueba que se bifurca a la versión de "un solo paso" si (A) el bucle tiene una longitud inferior a 8, o (B) no tenemos soporte para SSE3 / SSE4.
La versión de un solo paso es:
$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
que no tiene instrucciones SSE. Sin embargo, la parte importante es la caída a través. Si eax
(el parámetro de iteración) pasa 10
, cae en:
; Line 28
movd xmm0, edx
pshufd xmm0, xmm0, 0
pmaxsd xmm1, xmm0
que es el código que encuentra el máximo de los resultados de la versión de un solo paso y los resultados del SSE4. La tercera instrucción es pmaxsd
, que es una instrucción SSE4_1, y no está protegida por __isa_available
.
¿Existe una configuración o solución alternativa del compilador que pueda dejar intacta la vectorización automática, mientras no se invocan las instrucciones SSE4_1 en las computadoras habilitadas para Core2 SSE2? ¿Hay un error en mi código que está causando que esto suceda?
Tenga en cuenta que mis intentos de eliminar la naturaleza de doble bucle del bucle parecen hacer que el problema desaparezca.