visual studio net generate diferencia debug create c++ visual-c++ visual-studio-2012 visual-studio-2013 compiler-bug

c++ - net - Visual Studio 2012 valores diferentes Release/Debug mode



visual studio create build (4)

Comparé el código asm para ambos casos (en VC ++ 2013 express), en la compilación de lanzamiento, el código asm en la compilación de lanzamiento del ciclo for

for (int i = 0; i< Period; i++)

está debajo, y es muy diferente al de la versión de depuración

$LL6@main: ; 23 : sum = 0; ; 24 : for (int i = 0; i< Period; i++){ xorps xmm5, xmm5 lea eax, DWORD PTR [edi+88] xorps xmm4, xmm4 mov ecx, 3 npad 2 $LL3@main: ; 25 : //cout << "hi"; ; 26 : sum += A[bars - i]; movd xmm2, DWORD PTR [eax-4] lea eax, DWORD PTR [eax-32] movd xmm0, DWORD PTR [eax+32] movd xmm1, DWORD PTR [eax+36] movd xmm3, DWORD PTR [eax+40] punpckldq xmm3, xmm0 movd xmm0, DWORD PTR [eax+48] punpckldq xmm1, xmm2 movd xmm2, DWORD PTR [eax+44] punpckldq xmm3, xmm1 movd xmm1, DWORD PTR [eax+52] paddd xmm5, xmm3 movd xmm3, DWORD PTR [eax+56] punpckldq xmm3, xmm0 punpckldq xmm1, xmm2 punpckldq xmm3, xmm1 paddd xmm4, xmm3 dec ecx jne SHORT $LL3@main ; 23 : sum = 0; ; 24 : for (int i = 0; i< Period; i++){ paddd xmm4, xmm5 xor edx, edx movdqa xmm0, xmm4 mov eax, edi psrldq xmm0, 8 mov esi, 3 paddd xmm4, xmm0 movdqa xmm0, xmm4 psrldq xmm0, 4 paddd xmm4, xmm0 movd ebx, xmm4 npad 7 $LL30@main: ; 25 : //cout << "hi"; ; 26 : sum += A[bars - i]; add ecx, DWORD PTR [eax] lea eax, DWORD PTR [eax-8] add edx, DWORD PTR [eax+4] dec esi jne SHORT $LL30@main ; 27 : }

Como puede hacerlo desde el código asm, aquí se usan las instrucciones SSE. Así que revisé las opciones del compilador para las instrucciones SSE en VC ++, luego especifiqué / arch: IA32 para deshabilitar la generación de instrucciones SSE y SSE2 para los procesadores x86 en la compilación de lanzamiento, luego obtuve el mismo resultado que la compilación de depuración.

No estoy familiarizado con SSE, espero que alguien pueda explicar más en base a mis hallazgos.

Este código produce valores diferentes en MSVS 2012, Windows 7, al cambiar entre los modos Depuración y Liberación:

#include <iostream> using namespace std; int A[20000]; int main() { int shift = 0; int Period = 30; //Fill array for(int i = 0; i < 20000; i++) { A[i] = i * 2 + 123; } int sumTotal = 0; int sum = 0; for(int bars = Period + 10; bars < 1000; bars++) { sum = 0; for(int i = 0; i< Period; i++) { sum += A[bars - i]; } sumTotal += sum; } cout << sumTotal << endl; }

¿Puedes reproducir o encontrar por qué? He estado probando con todo tipo de ajustes en las propiedades del proyecto.

  • Depurar (el resultado correcto): 32630400
  • Publicación: 32814720

/GS /GL /analyze- /W3 /Gy /Zc:wchar_t /I"C:/Program Files (x86)/Visual Leak Detector/include" /Z7 /Gm- /O2 /Fd"Release/vc110.pdb" /fp:precise /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /errorReport:prompt /WX- /Zc:forScope /Gd /Oy- /Oi /MD /Fa"Release/" /EHsc /nologo /Fo"Release/" /Fp"Release/Testing.pch"


Creo que has encontrado un error en el optimizador. Puede obtener una compilación de lanzamiento para proporcionar el mismo resultado (correcto) que una depuración editando las optimizaciones o agregando un código adicional con efectos secundarios que no se pueden optimizar (por ejemplo, cout << "hi" ) dentro de lo más interno for loop (esto presumiblemente evita que la optimización se realice incorrectamente de otra manera). Sugeriría que se lo informe a Microsoft.

Actualización: Microsoft confirma que se trata de un error relacionado con la auto-vectorización y que se ha solucionado en la actualización VS2013 2. Una solución en otras versiones es deshabilitar la vectorización prefijando el ciclo con #pragma loop(no_vector) .

Además, describen dos construcciones de bucle diferentes que pueden desencadenar el error. Voy a citarlos:

Hay dos casos en los que se inicia el error:

1) Como mencionó el usuario burzvingion, los bucles que se vectorizan de la forma:

for (int i = 0; ...) {sum = A [...] - suma; }

2) Bucles que se vectorizan de la forma:

for (int i = 0; ...) {suma = suma + A [- i]; }

También dan la siguiente sugerencia para ubicar código vulnerable:

Si está buscando en su código fuente para intentar encontrar estos casos, le recomiendo que empiece lanzando / Qvec-report: 1 para encontrar todos los bucles que se vectorizaron, y que van desde allí. Para resolver los errores, coloque #pragma loop (no_vector) arriba de los for-loops.


El código que produce un error de optimización se puede reducir a lo siguiente:

#include <iostream> using namespace std; #define SIZE 12 int main() { int A[SIZE] = {0}; int sum = 0; for (int i=0; i<SIZE; i++) sum += A[SIZE-1-i]; cout << sum << endl; return 0; }

El error de optimización puede eliminarse aplicando uno de los siguientes cambios:

  1. Cambiar la definición de SIZE a un valor inferior a 12
  2. Cambia la expresión A[SIZE-1-i] a A[SIZE-i-1]
  3. Mueva la operación cout << sum << endl al ciclo

Entonces, para diagnosticar el problema, simplemente podemos aplicar uno de estos cambios y luego comparar el desensamblaje del código antes del cambio y el desensamblaje del código después del cambio.


Probé la versión "reducida" del código utilizando el compilador VS2012 C

int main() { int A[12] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; int sum = 0; int i; for (i = 0; i < 12; ++i) sum += A[11 - i]; printf("%d/n", sum); return 0; }

Lo compilé en modo x64. Config de lanzamiento optimizado para velocidad. El error todavía está allí, pero dependiendo de otras configuraciones de optimización y generación de código, se revela de manera diferente. Una versión del código generó un resultado "aleatorio", mientras que otra produjo consistentemente 8 como la suma (en lugar de los 12 propios).

Este es el aspecto del código generado para la versión que produce de manera consistente 8

000000013FC81DF0 mov rax,rsp 000000013FC81DF3 sub rsp,68h 000000013FC81DF7 movd xmm1,dword ptr [rax-18h] 000000013FC81DFC movd xmm2,dword ptr [rax-10h] 000000013FC81E01 movd xmm5,dword ptr [rax-0Ch] 000000013FC81E06 xorps xmm0,xmm0 000000013FC81E09 xorps xmm3,xmm3 for (i = 0; i < 12; ++i) 000000013FC81E0C xor ecx,ecx 000000013FC81E0E mov dword ptr [rax-48h],1 000000013FC81E15 mov dword ptr [rax-44h],1 000000013FC81E1C mov dword ptr [rax-40h],1 000000013FC81E23 punpckldq xmm2,xmm1 000000013FC81E27 mov dword ptr [rax-3Ch],1 000000013FC81E2E mov dword ptr [rax-38h],1 000000013FC81E35 mov dword ptr [rax-34h],1 { sum += A[11 - i]; 000000013FC81E3C movdqa xmm4,xmmword ptr [__xmm@00000001000000010000000100000001 (013FC83360h)] 000000013FC81E44 paddd xmm4,xmm0 000000013FC81E48 movd xmm0,dword ptr [rax-14h] 000000013FC81E4D mov dword ptr [rax-30h],1 000000013FC81E54 mov dword ptr [rax-2Ch],1 000000013FC81E5B mov dword ptr [rax-28h],1 000000013FC81E62 mov dword ptr [rax-24h],1 000000013FC81E69 punpckldq xmm5,xmm0 000000013FC81E6D punpckldq xmm5,xmm2 000000013FC81E71 paddd xmm5,xmm3 000000013FC81E75 paddd xmm5,xmm4 000000013FC81E79 mov dword ptr [rax-20h],1 000000013FC81E80 mov dword ptr [rax-1Ch],1 000000013FC81E87 mov r8d,ecx 000000013FC81E8A movdqa xmm0,xmm5 000000013FC81E8E psrldq xmm0,8 000000013FC81E93 paddd xmm5,xmm0 000000013FC81E97 movdqa xmm0,xmm5 000000013FC81E9B lea rax,[rax-40h] 000000013FC81E9F mov r9d,2 000000013FC81EA5 psrldq xmm0,4 000000013FC81EAA paddd xmm5,xmm0 000000013FC81EAE movd edx,xmm5 000000013FC81EB2 nop word ptr [rax+rax] { sum += A[11 - i]; 000000013FC81EC0 add ecx,dword ptr [rax+4] 000000013FC81EC3 add r8d,dword ptr [rax] 000000013FC81EC6 lea rax,[rax-8] 000000013FC81ECA dec r9 000000013FC81ECD jne main+0D0h (013FC81EC0h) } printf("%d/n", sum); 000000013FC81ECF lea eax,[r8+rcx] 000000013FC81ED3 lea rcx,[__security_cookie_complement+8h (013FC84040h)] 000000013FC81EDA add edx,eax 000000013FC81EDC call qword ptr [__imp_printf (013FC83140h)] return 0; 000000013FC81EE2 xor eax,eax } 000000013FC81EE4 add rsp,68h 000000013FC81EE8 ret

Hay muchas cosas extrañas y aparentemente innecesarias que el generador de códigos y el optimizador le sobran, pero lo que este código hace se puede describir brevemente de la siguiente manera.

Hay dos algoritmos independientes allí empleados para producir la suma final, que aparentemente se supone que procesan diferentes partes de la matriz. Supongo que dos flujos de procesamiento (no SSE y SSE) se utilizan para promover el paralelismo a través de la canalización de instrucciones.

Un algoritmo es un ciclo simple, que suma elementos de matriz, procesando dos elementos por iteración. Se puede extraer del código "intercalado" anterior de la siguiente manera

; Initialization 000000013F1E1E0C xor ecx,ecx ; ecx - odd element sum 000000013F1E1E87 mov r8d,ecx ; r8 - even element sum 000000013F1E1E9B lea rax,[rax-40h] ; start from i = 2 000000013F1E1E9F mov r9d,2 ; do 2 iterations ; The cycle 000000013F1E1EC0 add ecx,dword ptr [rax+4] ; ecx += A[i + 1] 000000013F1E1EC3 add r8d,dword ptr [rax] ; r8d += A[i] 000000013F1E1EC6 lea rax,[rax-8] ; i -= 2 000000013F1E1ECA dec r9 000000013F1E1ECD jne main+0D0h (013F1E1EC0h) ; loop again if r9 is not zero

Este algoritmo comienza a agregar elementos de dirección rax - 40h , que en mi experimento fue igual a &A[2] y hace dos iteraciones hacia atrás salteando hacia atrás sobre dos elementos. Esto acumula la suma de A[0] y A[2] en el registro r8 y suma de A[1] y A[3] en el registro ecx . Por lo tanto, esta parte del algoritmo procesa 4 elementos de la matriz y genera correctamente los valores 2 en r8 y ecx .

La otra parte del algoritmo se escribe usando instrucciones SSE y es aparentemente responsable de sumar la porción restante de la matriz. Se puede extraer del código de la siguiente manera

; Initially xmm5 is zero 000000013F1E1E3C movdqa xmm4,xmmword ptr [__xmm@00000001000000010000000100000001 (013F1E3360h)] 000000013F1E1E75 paddd xmm5,xmm4 000000013F1E1E8A movdqa xmm0,xmm5 ; copy 000000013F1E1E8E psrldq xmm0,8 ; shift 000000013F1E1E93 paddd xmm5,xmm0 ; and add 000000013F1E1E8A movdqa xmm0,xmm5 ; copy 000000013F1E1E8E psrldq xmm0,4 ; shift 000000013F1E1E93 paddd xmm5,xmm0 ; and add 000000013F1E1EAE movd edx,xmm5 ; edx - the sum

El algoritmo general utilizado por esa parte es simple: coloca el valor 0x00000001000000010000000100000001 en el registro xmm5 128 bits, luego lo desplaza 8 bytes hacia la derecha ( 0x00000000000000000000000100000001 ) y lo agrega al valor original, produciendo 0x00000001000000010000000200000002 . Esto se desplaza nuevamente 4 bytes hacia la derecha ( 0x00000000000000010000000100000002 ) y se vuelve a agregar al valor anterior, produciendo 0x00000001000000020000000300000004 . La última palabra de 32 bits 0x00000004 de xmm5 se toma como resultado y se coloca en el registro de edx . Por lo tanto, este algoritmo produce 4 como resultado final. Es bastante obvio que este algoritmo simplemente realiza la adición "paralela" de palabras consecutivas de 32 bits en un registro de 128 bits. Tenga en cuenta, por cierto, que este algoritmo ni siquiera intenta acceder a A , comienza a sumar desde una constante integrada producida por el compilador / optimizador.

Ahora, al final, el valor de r8 + ecx + edx se informa como la suma final. Obviamente, esto es solo 8 , en lugar de los 12 correctos. Parece que uno de estos dos algoritmos olvidó hacer parte de su trabajo. No sé cuál, pero a juzgar por la abundancia de instrucciones "redundantes", parece que fue el algoritmo SSE el que se suponía que generaría 8 en edx en lugar de 4 . Una instrucción sospechosa es esta

000000013FC81E71 paddd xmm5,xmm3

En ese momento xmm3 siempre contiene cero. Entonces, esta instrucción parece completamente redundante e innecesaria. Pero si xmm3 realidad contenía otra constante "mágica" que representa otros 4 elementos de la matriz (como xmm4 hizo xmm4 ), entonces el algoritmo funcionaría correctamente y produciría la suma adecuada.

Si uno usa valores iniciales distintivos para elementos de matriz

int A[12] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };

se puede ver claramente que el primer algoritmo (no SSE) suma exitosamente 1, 2, 3, 4 , mientras que el segundo algoritmo (SSE) suma 9, 10, 11, 12 . 5, 6, 7, 8 permanecen excluidos de la consideración, lo que resulta en 52 como suma final en lugar de 78 correcta.

Esto es definitivamente un error de compilador / optimizador.

PD: el mismo proyecto con la misma configuración importada en VS2013 Actualización 2 no parece sufrir este error.