53 votes

Pourquoi le mulss ne prend-il que 3 cycles sur Haswell, différent des tableaux d'instructions d'Agner?

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

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