Je suis un newbie à l'instruction de l'optimisation.
J'ai fait une analyse simple sur une fonction simple dotp qui est utilisé pour obtenir le produit scalaire de deux flotteur tableaux.
Le code C est comme suit:
float dotp(
const float x[],
const float y[],
const short n
)
{
short i;
float suma;
suma = 0.0f;
for(i=0; i<n; i++)
{
suma += x[i] * y[i];
}
return suma;
}
J'utilise le cadre de test fourni par Agner de Brouillard sur le web testp n'.
Les tableaux qui sont utilisés dans ce cas sont alignés:
int n = 2048;
float* z2 = (float*)_mm_malloc(sizeof(float)*n, 64);
char *mem = (char*)_mm_malloc(1<<18,4096);
char *a = mem;
char *b = a+n*sizeof(float);
char *c = b+n*sizeof(float);
float *x = (float*)a;
float *y = (float*)b;
float *z = (float*)c;
Puis-je appeler la fonction dotp, n=2048, répétez=100000:
for (i = 0; i < repeat; i++)
{
sum = dotp(x,y,n);
}
Je compile avec gcc 4.8.3, avec l'option de compilation -O3.
Je compile cette application sur un ordinateur qui ne prend pas en charge FMA instructions, de sorte que vous pouvez le voir il y a seulement les instructions SSE.
L'assemblée de code:
.L13:
movss xmm1, DWORD PTR [rdi+rax*4]
mulss xmm1, DWORD PTR [rsi+rax*4]
add rax, 1
cmp cx, ax
addss xmm0, xmm1
jg .L13
Je fais un peu d'analyse:
μops-fused la 0 1 2 3 4 5 6 7
movss 1 3 0.5 0.5
mulss 1 5 0.5 0.5 0.5 0.5
add 1 1 0.25 0.25 0.25 0.25
cmp 1 1 0.25 0.25 0.25 0.25
addss 1 3 1
jg 1 1 1 -----------------------------------------------------------------------------
total 6 5 1 2 1 1 0.5 1.5
Après l'exécution, nous obtenons le résultat:
Clock | Core cyc | Instruct | BrTaken | uop p0 | uop p1
--------------------------------------------------------------------
542177906 |609942404 |1230100389 |205000027 |261069369 |205511063
--------------------------------------------------------------------
2.64 | 2.97 | 6.00 | 1 | 1.27 | 1.00
uop p2 | uop p3 | uop p4 | uop p5 | uop p6 | uop p7
-----------------------------------------------------------------------
205185258 | 205188997 | 100833 | 245370353 | 313581694 | 844
-----------------------------------------------------------------------
1.00 | 1.00 | 0.00 | 1.19 | 1.52 | 0.00
La deuxième ligne est la valeur lue à partir de l'Intel registres; la troisième ligne est divisée par le numéro de la succursale, "BrTaken".
Ainsi, nous pouvons voir, dans la boucle il y a 6 instructions, 7 uop, en accord avec l'analyse.
Le nombre de uop exécuter dans port0 port1 port 5 port6 sont similaires à ce que l'analyse a dit. Je pense que peut-être l'uop planificateur de cela, il peut essayer d'équilibrer les charges sur les ports, ai-je le droit?
Je ne comprends absolument pas savoir pourquoi il y a seulement environ 3 cycles par boucle. Selon Agner de l' instruction de la table, le temps de latence de l'instruction mulss
est de 5, et il y a des dépendances entre les boucles, donc autant que je le vois il devrait prendre au moins 5 cycles par boucle.
Quelqu'un pourrait-il apporter un aperçu?
==================================================================
J'ai essayé d'écrire une version optimisée de cette fonction dans les msna, de dérouler la boucle par un facteur de 8 et à l'aide de l' vfmadd231ps
enseignement:
.L2:
vmovaps ymm1, [rdi+rax]
vfmadd231ps ymm0, ymm1, [rsi+rax]
vmovaps ymm2, [rdi+rax+32]
vfmadd231ps ymm3, ymm2, [rsi+rax+32]
vmovaps ymm4, [rdi+rax+64]
vfmadd231ps ymm5, ymm4, [rsi+rax+64]
vmovaps ymm6, [rdi+rax+96]
vfmadd231ps ymm7, ymm6, [rsi+rax+96]
vmovaps ymm8, [rdi+rax+128]
vfmadd231ps ymm9, ymm8, [rsi+rax+128]
vmovaps ymm10, [rdi+rax+160]
vfmadd231ps ymm11, ymm10, [rsi+rax+160]
vmovaps ymm12, [rdi+rax+192]
vfmadd231ps ymm13, ymm12, [rsi+rax+192]
vmovaps ymm14, [rdi+rax+224]
vfmadd231ps ymm15, ymm14, [rsi+rax+224]
add rax, 256
jne .L2
Le résultat:
Clock | Core cyc | Instruct | BrTaken | uop p0 | uop p1
------------------------------------------------------------------------
24371315 | 27477805| 59400061 | 3200001 | 14679543 | 11011601
------------------------------------------------------------------------
7.62 | 8.59 | 18.56 | 1 | 4.59 | 3.44
uop p2 | uop p3 | uop p4 | uop p5 | uop p6 | uop p7
-------------------------------------------------------------------------
25960380 |26000252 | 47 | 537 | 3301043 | 10
------------------------------------------------------------------------------
8.11 |8.13 | 0.00 | 0.00 | 1.03 | 0.00
Nous pouvons donc voir le cache de données L1 atteindre les 2*256 bits/8.59, il est très proche de la peak 2*256/8, l'usage est d'environ 93%, le FMA unité utilisée uniquement 8/8.59, le pic est de 2*8/8, l'usage est de 47%.
Donc, je pense que j'ai atteint le L1D goulot d'étranglement que Peter Cordes attend.
==================================================================
Un merci spécial à Boann, fixer pour de nombreuses fautes d'orthographe dans ma question.
=================================================================
À partir de la réponse de Pierre, j'obtiens que la "lu et écrit" registre de la dépendance, écrivain "seulement" registres ne serait pas la dépendance.
J'ai donc essayer de réduire les registres utilisés dans la boucle, et j'essaie de dérouler par 5, si tout est ok, je dois rencontrer le même goulot d'étranglement, L1D.
.L2:
vmovaps ymm0, [rdi+rax]
vfmadd231ps ymm1, ymm0, [rsi+rax]
vmovaps ymm0, [rdi+rax+32]
vfmadd231ps ymm2, ymm0, [rsi+rax+32]
vmovaps ymm0, [rdi+rax+64]
vfmadd231ps ymm3, ymm0, [rsi+rax+64]
vmovaps ymm0, [rdi+rax+96]
vfmadd231ps ymm4, ymm0, [rsi+rax+96]
vmovaps ymm0, [rdi+rax+128]
vfmadd231ps ymm5, ymm0, [rsi+rax+128]
add rax, 160 ;n = n+32
jne .L2
Le résultat:
Clock | Core cyc | Instruct | BrTaken | uop p0 | uop p1
------------------------------------------------------------------------
25332590 | 28547345 | 63700051 | 5100001 | 14951738 | 10549694
------------------------------------------------------------------------
4.97 | 5.60 | 12.49 | 1 | 2.93 | 2.07
uop p2 |uop p3 | uop p4 | uop p5 |uop p6 | uop p7
------------------------------------------------------------------------------
25900132 |25900132 | 50 | 683 | 5400909 | 9
-------------------------------------------------------------------------------
5.08 |5.08 | 0.00 | 0.00 |1.06 | 0.00
Nous pouvons voir 5/5.60 = 89.45%, c'est un peu plus petite que la urolling par 8, est-il quelque chose de mal?
=================================================================
J'essaie de dérouler la boucle de 6, 7 et 15, pour voir le résultat. J'ai aussi dérouler en 5 et 8, en double afin de confirmer le résultat.
Le résultat est comme suit, on peut voir cette fois, le résultat est beaucoup mieux qu'avant.
Bien que le résultat n'est pas stable, le déroulement facteur est le plus grand et le résultat est meilleur.
| L1D bandwidth | CodeMiss | L1D Miss | L2 Miss
----------------------------------------------------------------------------
unroll5 | 91.86% ~ 91.94% | 3~33 | 272~888 | 17~223
--------------------------------------------------------------------------
unroll6 | 92.93% ~ 93.00% | 4~30 | 481~1432 | 26~213
--------------------------------------------------------------------------
unroll7 | 92.29% ~ 92.65% | 5~28 | 336~1736 | 14~257
--------------------------------------------------------------------------
unroll8 | 95.10% ~ 97.68% | 4~23 | 363~780 | 42~132
--------------------------------------------------------------------------
unroll15 | 97.95% ~ 98.16% | 5~28 | 651~1295 | 29~68
=====================================================================
J'essaie de compiler avec gcc 7.1 dans le web "https://gcc.godbolt.org"
L'option de compilation est "-O3-mars=haswell -mtune=intel", qui est similaire à la gcc 4.8.3.
.L3:
vmovss xmm1, DWORD PTR [rdi+rax]
vfmadd231ss xmm0, xmm1, DWORD PTR [rsi+rax]
add rax, 4
cmp rdx, rax
jne .L3
ret