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:
- Cambiar la definición de
SIZE
a un valor inferior a 12 - Cambia la expresión
A[SIZE-1-i]
aA[SIZE-i-1]
- 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.