33 votes

Visual Studio 2012 des valeurs Release/Debug mode différentes

Ce code produit des valeurs différentes dans MSVS 2012, Windows 7, lors du passage entre le mode Debug et Release :

#include 
using namespace std;

int A[20000];

int main() {

    int shift = 0;
    int Period = 30;
    //Remplir le tableau
    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;
}

Pouvez-vous reproduire le problème ou le trouver ? J'ai testé avec toutes sortes de paramètres dans les propriétés du projet.

  • Debug (le résultat correct) : 32630400
  • Release : 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"

18voto

AndreyT Points 139512

J'ai testé la "réduction" de la version du code à l'aide de VS2012 compilateur 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;
}

J'ai compilé dans x64 mode de Libération config optimisé pour la vitesse. Le bug est toujours là, mais selon d'autres l'optimisation de la génération de code et les paramètres de il se révèle différemment. Une version du code généré "aléatoire" résultat, tandis qu'un autre produit constamment 8 comme la somme (au lieu de la bonne 12).

C'est ce que le code généré ressemble à la version qui produit constamment 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  

Il y a beaucoup d'étrange et apparemment inutiles mumbo-jumbo-il par le générateur de code et d'optimisation, mais ce que ce code ne peut être brièvement décrit comme suit.

Il y a deux indépendants des algorithmes employés pour produire la somme finale, qui apparemment sont censés traiter les différentes parties du tableau. Je suppose que les deux flux de transformation (non-SSE et SSE) sont utilisés pour promouvoir le parallélisme d'instructions de traitement en pipeline.

Un algorithme est un cycle simple, qui résume les éléments du tableau, le traitement de deux éléments par itération. Il peut être extrait à partir de ci-dessus "entrelacé" code comme suit

; 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 

Cet algorithme commence par l'ajout d'éléments à partir de l'adresse rax - 40h, ce qui dans mon expérience a été égal à &A[2] et deux itérations en arrière reculer de plus de deux éléments. Cela s'accumule, la somme des A[0] et A[2] dans le registre r8 et la somme des A[1] et A[3] dans le registre ecx. Ainsi, cette partie de l'algorithme traite les 4 éléments de la matrice et correctement génère des valeurs 2 dans les deux r8 et ecx.

L'autre partie de l'algorithme est écrit en utilisant le jeu d'instructions SSE et est apparemment responsable de la somme de la partie restante de la pile. Il peut être extrait à partir du code comme suit

; 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

L'algorithme général utilisé par cette partie est simple: elle met valeur 0x00000001000000010000000100000001 en 128 bits registre xmm5, puis décale 8 octets vers la droite (0x00000000000000000000000100000001) et l'ajoute à la valeur d'origine, produisant 0x00000001000000010000000200000002. C'est à nouveau changé de 4 octets pour le droit (0x00000000000000010000000100000002) et ajoutée à la valeur précédente de nouveau, produisant 0x00000001000000020000000300000004. Le dernier mot de 32 bits 0x00000004 de xmm5 est considéré comme le résultat et placé dans le registre edx. Ainsi, cet algorithme produit 4 que son résultat final. Il est assez évident que cet algorithme effectue simplement le "parallèle" outre consécutives de 32 bits des mots de 128 bits registre. Remarque, d'ailleurs, que cet algorithme ne prend même pas tenter d'accéder A, il commence résumant à partir d'un dérivé incorporé constant produit par le compilateur/optimiseur.

Maintenant, à la fin de la valeur de r8 + ecx + edx est rapporté que la somme finale. Évidemment, ce n'est qu' 8, au lieu de le corriger 12. Il ressemble à l'une de ces deux algorithmes ont oublié de faire une partie de son travail. Je ne sais pas lequel, mais à en juger par l'abondance des "redondant" instructions qu'il semble que c'était l'ESS algorithme qui était censé générer 8 en edx au lieu de 4. Un suspect d'instruction est-ce un

000000013FC81E71  paddd       xmm5,xmm3  

À ce moment - xmm3 contient toujours zéro. Ainsi, cette instruction semble complètement redondant et inutile. Mais si xmm3 contenaient en fait un autre "magie" de la constante représentant les 4 éléments de la matrice (comme xmm4 a), alors l'algorithme fonctionne correctement et produire de la bonne somme.

Si l'on utilise distinctif des valeurs initiales pour les éléments du tableau

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

il peut être vu clairement que le premier (non-SSE) de l'algorithme avec succès les sommes 1, 2, 3, 4, tandis que le second (SSE) d'algorithme résume 9, 10, 11, 12. 5, 6, 7, 8 restent exclus de l'examen, résultant en 52 comme somme finale au lieu de le corriger 78.

C'est certainement l'un compilateur/optimiseur de bug.

P. S. Le même projet avec les mêmes paramètres importés dans VS2013 mise à Jour 2 ne semble pas souffrir de ce bug.

15voto

dlf Points 4140

Je crois que vous avez trouvé un bug dans l'optimiseur. Vous pouvez obtenir une version de production pour donner la même sortie (correcte) qu'une version de débogage en désactivant les optimisations ou en ajoutant du code supplémentaire avec des effets secondaires qui ne peuvent pas être optimisés (par exemple, cout << "hi") à l'intérieur de la boucle for la plus interne (cela empêche apparemment toute optimisation incorrectement effectuée par ailleurs). Je vous suggère de signaler cela à Microsoft.


Mise à jour : Microsoft confirme qu'il s'agit d'un bug lié à l'auto-vectorisation et qu'il a été corrigé dans la mise à jour VS2013 2. Une solution de contournement dans d'autres versions consiste à désactiver la vectorisation en préfixant la boucle avec #pragma loop(no_vector).

De plus, ils décrivent deux constructions de boucles différentes qui peuvent déclencher le bug. Je vais simplement les citer :

Il y a deux cas où le bug se déclenche :

1) Comme l'a mentionné l'utilisateur burzvingion, les boucles qui sont vectorisées de la forme :

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

2) Boucles qui sont vectorisées de la forme :

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

Ils donnent également la suggestion suivante pour localiser le code vulnérable :

Si vous parcourez votre code source pour essayer de trouver ces cas, je vous recommande de commencer par lancer /Qvec-report:1 pour trouver toutes les boucles qui ont été vectorisées, et de partir de là. Pour contourner les bugs, placez #pragma loop(no_vector) au-dessus des boucles for.

5voto

barak manos Points 10969

Le code qui produit une erreur d'optimisation peut être réduit à ce qui suit :

#include 
using namespace std;

#define SIZE 12

int main()
{
    int A[SIZE] = {0};

    int sum = 0;
    for (int i=0; i

``

L'erreur d'optimisation peut être corrigée en appliquant l'un des changements suivants :

  1. Changer la définition de SIZE à une valeur inférieure à 12
  2. Changer l'expression A[SIZE-1-i] en A[SIZE-i-1]
  3. Déplacer l'opération cout << sum << endl dans la boucle

Donc, pour diagnostiquer le problème, nous pouvons simplement appliquer l'un de ces changements, puis comparer entre le désassemblage du code avant le changement et le désassemblage du code après le changement.

``

4voto

Matt Points 3236

J'ai comparé le code asm pour les deux cas (dans VC++ 2013 Express), dans la version release, le code asm dans la version release de la boucle for

for (int i = 0; i< Période; i++)

est ci-dessous, et il est très différent de celui dans la version debug

$LL6@main:

; 23   :        sum = 0;
; 24   :        for (int i = 0; i< Période; 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< Période; 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   :    

}

Comme vous pouvez le constater à partir du code asm, des instructions SSE sont utilisées ici. J'ai donc vérifié les options du compilateur pour les instructions SSE dans VC++, puis j'ai spécifié /arch:IA32 pour désactiver la génération d'instructions SSE et SSE2 pour les processeurs x86 dans la version release, puis j'ai obtenu le même résultat que dans la version debug.

Je ne suis pas familier avec SSE, j'espère que quelqu'un pourra expliquer plus en se basant sur mes découvertes.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X