c++ c++11 visual-studio-2013 sse

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.