2016-06-06 28 views
2

Ich habe eine einfache n x n Matrixmultiplikation implementiert, um gleiche Leistungsabstimmungen in c mit OpenMp zu testen. Mein erster Code ist der folgende:Xeon Phi: langsamere Leistung mit Padding

#pragma omp parallel for shared(a,b,c) private(h,i,j,k) 
    for(i = 0; i < n; i++) 
    { 
     for(j = 0; j < n; j++) 
     { 
      for(k = 0; k < n; k++) 
      { 
       a[i*n+j]+= b[i*n+k] * c[k*n+j]; 

Der Compiler schaltet die j und k-Schleife, so dass ich den ikj-Algorithmus. Das erste, was ich implementieren wollte, war das Auffüllen, um jede Zeile für den allierten Cache-Zugriff auf 64 Byte zu verteilen. Daher berechnete ich die benötigte zusätzliche Größe für jede Zeile. Mit einer Zeilenlänge von 5900 ist die neue Größe 5904 (Referenz ist ISBN-9780124104143). Mein neuer Code ist folgender:

#pragma omp parallel for shared(a,b,c) private(h,i,j,k) 
    for(i = 0; i < n; i++) 
    { 
     for(k = 0; k < n; k++) 
     { 
      #pragma simd 
      #pragma unroll(4) 
      for(j = 0; j < n; j++) 
      { 
       a[i*pn+j]+= b[i*pn+k] * c[k*pn+j]; 

wo pn ist die neue, gepolsterte Zeilenlänge. Ich musste meine Schleifen manuell permutieren, weil der Compiler es ablehnte. Wenn ich diesen Code auf einem normalen Xeon-Prozessor laufe, bekomme ich fast die gleiche Leistung wie vorher, vielleicht ein bisschen besser. Das ist was ich erwartet habe. Aber wenn ich den Code auf einem Xeon Phi laufen lasse, ist es ungefähr 1/10 des ursprünglichen Codes.

Nach weiteren Compileruntersuchungen fiel mir auf, dass die innere Schleife nicht mehr entrollt und vektorisiert wird. Also fügte ich die folgenden #pragmas hinzu:

#pragma simd 
#pragma unroll 

Die Vektorisierung funktioniert gut, aber die Restschleife wird nicht abgerollt. Die Leistung ist viel besser, aber immer noch nur etwa die Hälfte der normalen Version.

Hier ist die Compiler (O3) Ausgabe des normalen Code:

LOOP BEGIN at mm_par.c(75,3) 
    remark #25444: Loopnest Interchanged: (1 2 3) --> (1 3 2) 

    LOOP BEGIN at mm_par.c(79,5) 
    remark #25460: No loop optimizations reported 

    LOOP BEGIN at mm_par.c(77,4) 
    remark #15301: PERMUTED LOOP WAS VECTORIZED 
    LOOP END 

    LOOP BEGIN at mm_par.c(77,4) 
    <Remainder> 
    remark #25436: completely unrolled by 4 
    LOOP END 
    LOOP END 
    LOOP END 

Und hier der Ausgang des gepolsterten einer mit SIMD und Abrollen Pragmas:

LOOP BEGIN at mm_ali.c(76,3) 
remark #25460: No loop optimizations reported 

LOOP BEGIN at mm_ali.c(78,4) 
remark #25460: No loop optimizations reported 

    LOOP BEGIN at mm_ali.c(82,10) 
    remark #15301: SIMD LOOP WAS VECTORIZED 
    LOOP END 
LOOP END 
LOOP END 

So das Abrollen bekommt ignoriert. Gibt es eine Möglichkeit, es zu erzwingen? Ich frage mich auch, wenn das ist der einzige Grund für die schlechte Leistung ..

edit: Die Assambly für die schnelle Matrixmultiplikation ohne Polsterung sieht wie folgt aus:

vmovapd c(%r15,%rbx,8), %zmm1       #81.28 c1 
    vprefetche1 2048+a(%r11,%rbx,8)       #81.6 c5 
    vmovapd 64+c(%r15,%rbx,8), %zmm3      #81.28 c9 
    vprefetch0 768+a(%r11,%rbx,8)       #81.6 c13 
    vmovapd 128+c(%r15,%rbx,8), %zmm4      #81.28 c17 
    vprefetch1 2048+c(%r15,%rbx,8)       #81.28 c21 
    vmovapd 192+c(%r15,%rbx,8), %zmm5      #81.28 c25 
    vprefetch0 768+c(%r15,%rbx,8)       #81.28 c29 
    vfmadd213pd a(%r11,%rbx,8), %zmm0, %zmm1    #81.6 c33 
    vprefetche1 2112+a(%r11,%rbx,8)       #81.6 c37 
    vfmadd213pd 64+a(%r11,%rbx,8), %zmm0, %zmm3    #81.6 c41 
    vprefetch0 832+a(%r11,%rbx,8)       #81.6 c45 
    vfmadd213pd 128+a(%r11,%rbx,8), %zmm0, %zmm4   #81.6 c49 
    vprefetch1 2112+c(%r15,%rbx,8)       #81.28 c53 
    vfmadd213pd 192+a(%r11,%rbx,8), %zmm0, %zmm5   #81.6 c57 
    vprefetch0 832+c(%r15,%rbx,8)       #81.28 c61 
    vmovaps %zmm1, a(%r11,%rbx,8)       #81.6 c65 
    vprefetche1 2176+a(%r11,%rbx,8)       #81.6 c69 
    vmovaps %zmm3, 64+a(%r11,%rbx,8)      #81.6 c73 
    vprefetch0 896+a(%r11,%rbx,8)       #81.6 c77 
    vmovaps %zmm4, 128+a(%r11,%rbx,8)      #81.6 c81 
    vprefetch1 2176+c(%r15,%rbx,8)       #81.28 c85 
    vmovaps %zmm5, 192+a(%r11,%rbx,8)      #81.6 c89 
    vprefetch0 896+c(%r15,%rbx,8)       #81.28 c93 
    vprefetche1 2240+a(%r11,%rbx,8)       #81.6 c97 
    vprefetch0 960+a(%r11,%rbx,8)       #81.6 c101 
    vprefetch1 2240+c(%r15,%rbx,8)       #81.28 c105 
    vprefetch0 960+c(%r15,%rbx,8)       #81.28 c109 
    addq  $32, %rbx          #77.4 c113 
    cmpq  %rsi, %rbx         #77.4 c117 
    jb  ..B1.51  # Prob 99%      #77.4 c117 

Der eine für die langsame Multiplikation mit Polsterung sieht so aus:

vloadunpackld (%rbx), %zmm0        #83.6 c1 
    addl  $32, %r15d         #81.10 c1 
    vprefetch1 2048+c(%rcx)         #83.30 c5 
    vloadunpackhd 64(%rbx), %zmm0       #83.6 c9 
    addq  $256, %rbx         #81.10 c9 
    vprefetch0 512+c(%rcx)         #83.30 c13 
    vbroadcastsd b(%r12,%r13,8), %zmm2      #83.18 c17 
    vprefetch1 2112+c(%rcx)         #83.30 c21 
    vfmadd132pd c(%rcx), %zmm0, %zmm2      #83.6 c25 
    vprefetch0 576+c(%rcx)         #83.30 c29 
    vpackstoreld %zmm2, (%rsi)        #83.6 c33 
    vprefetch1 2176+c(%rcx)         #83.30 c37 
    vpackstorehd %zmm2, 64(%rsi)       #83.6 c41 
    addq  $256, %rsi         #81.10 c41 
    vprefetch0 640+c(%rcx)         #83.30 c45 
    vloadunpackld (%rdi), %zmm3        #83.6 c49 
    vprefetch1 2240+c(%rcx)         #83.30 c53 
    vloadunpackhd 64(%rdi), %zmm3       #83.6 c57 
    addq  $256, %rdi         #81.10 c57 
    vprefetch0 704+c(%rcx)         #83.30 c61 
    vbroadcastsd b(%r12,%r13,8), %zmm4      #83.18 c65 
    vfmadd132pd 64+c(%rcx), %zmm3, %zmm4     #83.6 c69 
    nop              #83.6 c73 
    vpackstoreld %zmm4, (%r8)        #83.6 c77 
    vpackstorehd %zmm4, 64(%r8)        #83.6 c81 
    addq  $256, %r8          #81.10 c81 
    vloadunpackld (%r9), %zmm5        #83.6 c85 
    vloadunpackhd 64(%r9), %zmm5       #83.6 c89 
    addq  $256, %r9          #81.10 c89 
    vbroadcastsd b(%r12,%r13,8), %zmm6      #83.18 c93 
    vfmadd132pd 128+c(%rcx), %zmm5, %zmm6     #83.6 c97 
    nop              #83.6 c101 
    vpackstoreld %zmm6, (%r10)        #83.6 c105 
    vpackstorehd %zmm6, 64(%r10)       #83.6 c109 
    addq  $256, %r10         #81.10 c109 
    vloadunpackld (%r11), %zmm7        #83.6 c113 
    vloadunpackhd 64(%r11), %zmm7       #83.6 c117 
    addq  $256, %r11         #81.10 c117 
    vbroadcastsd b(%r12,%r13,8), %zmm8      #83.18 c121 
    vfmadd132pd 192+c(%rcx), %zmm7, %zmm8     #83.6 c125 
    addq  $256, %rcx         #81.10 c129 
    movb  %al, %al          #83.6 c129 
    vpackstoreld %zmm8, (%rdx)        #83.6 c133 
    vpackstorehd %zmm8, 64(%rdx)       #83.6 c137 
    addq  $256, %rdx         #81.10 c137 
    cmpl  $5888, %r15d         #81.10 c141 
    jb  ..B1.42  # Prob 99%      #81.10 c141 

Hier ist der vollständige Code meiner Lösung. Wenn ich np mit n tausche, ist die Leistung mehr als doppelt so schnell.

#include <sys/time.h> 
#include <omp.h> 

#ifndef max 
#define max(a,b) (((a) (b)) ? (a) : (b)) 
#define min(a,b) (((a) < (b)) ? (a) : (b)) 
#endif 

#define n 5900 
#define pn ((((n*sizeof(double))+63)/64)*(64/sizeof(double))) //padding 
#define threadNum 144 
#define loops 1 

double dtime() 
{ 
    double tseconds = 0.0; 
    struct timeval mytime; 
    gettimeofday(&mytime, (struct timezone*)0); 
    tseconds = (double)(mytime.tv_sec + mytime.tv_usec*1.0e-6); 
    return tseconds; 

} 

double a[n*pn] __attribute__((aligned(64))); 
double b[n*pn] __attribute__((aligned(64))); 
double c[n*pn] __attribute__((aligned(64))); 


main(int argc, char **argv){ 

    int threadNumber, loopNumber; 
    if(argc == 3) 
    { 
     threadNumber = atoi(argv[1]); 
     loopNumber = atoi(argv[2]); 
    } else 
    { 
     threadNumber = threadNum; 
     loopNumber = loops;  
    } 


    double tstart, tstop, ttime; 
    int i,j,k,h; 

    // initialize matrices 
    #pragma omp parallel for 
    for(i = 0; i < pn*n; i++) 
    { 
     a[i]=0.0; 
     b[i]=2.0; 
     c[i]=2.0; 
    } 

    omp_set_num_threads(threadNumber); 

    tstart = dtime(); 

    //parallelize via OpenMP on MIC 
    for(h = 0; h < loopNumber; h++){ 
     #pragma omp parallel for shared(a,b,c) private(h,i,j,k) 
     for(i = 0; i < n; i++) 
     { 
      for(k = 0; k < n; k++) 
      {   
       #pragma omp simd aligned(a, b, c: 64) 
       for(j = 0; j < n; j++)    
       { 
        a[i*pn+j]+= b[i*pn+k] * c[k*pn +j]; 
       } 
      } 
     } 

    } 

    tstop = dtime(); 
    double elapsed = tstop - tstart; 

    double mFlops = ((double)n)*n*n*2.0*loopNumber/elapsed*1.0e-06; 

    #pragma omp parallel 
    #pragma omp master 
    printf("%d %.3f\n", omp_get_num_threads(), mFlops); 

} 
+0

Wenn Sie auf dem Xeon Phi laufen, laufen Sie auf einem Kern oder allen? – EOF

+0

122 verstreute Fäden. Ich habe verschiedene Zahlen getestet, aber bei allen ist die zweite Implementierung langsamer. – ImmaCute

+1

Wie vergleicht die Matrix-Multiplikation mit einem 'memcpy()' der gleichen Größe und Ausrichtung? – EOF

Antwort

1

das Code-Snippet Sie für eine ordnungsgemäße Prüfung zu ermöglichen, zu klein gab kann ich auf eine Lösung vorschlagen, ohne es richtig zu testen, so könnte es kläglich scheitern. Wie auch immer, hier geht nichts.

Sie haben gesagt, dass Sie die Daten aufgefüllt haben, um die Zeilen auszurichten, aber, AFAICS, diese Information wird nie an den Compiler übertragen, der sie daher nicht ausnutzen kann. Dafür gibt es verschiedene Lösungen, aber da OpenMP-Direktiven bereits für die Parallelisierung existieren, scheint die Verwendung von etwas mehr für die Vektorisierung die naheliegende Wahl zu sein.

dass so würde Ihnen etwas wie folgt aus:

#pragma omp parallel for private(i, j, k) 
for(i = 0; i < n; i++) { 
    double *aa = &a[i * pn]; 
    double *bb = &b[i * pn]; 
    for(k = 0; k < n; k++) { 
     double *cc = &c[k * pn]; 
     #pragma omp simd aligned(aa, bb, cc: 64) 
     for(j = 0; j < n; j++) { 
      aa[j] += bb[k] * cc[j]; 
     } 
    } 
} 

Hier nahm ich Ihre Arrays waren double so könnten Sie das heißt, sie ändern müssen nicht. Abgesehen davon denke ich, dass die Idee da ist. Ich weiß nicht, ob es für Sie funktioniert, aber das ist definitiv einen Versuch wert.

+0

Vielen Dank für Ihren Vorschlag. Leider macht es nicht den Trick. Ich bekomme nur ein wenig Geschwindigkeit, wenn überhaupt. Ich habe den kompletten Quellcode zu meiner Frage hinzugefügt. Das wichtigste ist zu beachten: Die zweite Lösung ist nur langsamer auf einem Xeon Phi, nicht auf einem normalen Xeon Prozessor. – ImmaCute

+0

Ok, ich habe kein Phi zu testen, also kann ich das Ding nicht stimmen. Aber nur ein paar Bemerkungen: 'pn' ist ein ziemlich verworrenes Makro. Kannst du es nicht zumindest für Testzwecke durch seinen Wert 5904 ersetzen (wie du es für "n" tust)? 2/In Ihrem Code bezieht sich der wichtigste Teil der Ausrichtung auf die Zeilen, nicht auf das vollständige Array. Deshalb habe ich in meiner vorgeschlagenen Lösung die 'aa'' bb'- und 'cc'-Arrays eingeführt. Hast du es wirklich benutzt? – Gilles

+0

Jetzt habe ich und es gibt mir eine große Beschleunigung. Aber ich verstehe noch nicht, warum Zeiger schneller sind. Hast du eine Erklärung dafür? – ImmaCute