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.