2016-06-06 59 views
2

我已經實現了一個簡單的n×n矩陣乘法,用OpenMp測試c中的相同性能調試。我最初的代碼如下:Xeon Phi:填充性能較差

#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]; 

編譯器開關J和K循環,所以我得到了ikj算法。我想要實現的第一件事是填充以便將每行的全部字節均設爲64byte,以便進行全局緩存訪問。因此我計算了每行所需的額外大小。隨着5900行length,新的大小是5904(參考是ISBN-9780124104143)。我的新代碼如下:

#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]; 

其中pn是新的填充行長度。我不得不手動排列我的循環,因爲編譯器拒絕。在普通的Xeon處理器上運行此代碼,我獲得的性能與以前幾乎相同,可能稍微好一點。這就是我所期望的。但是當我在Xeon Phi上運行代碼時,它大約是初始代碼的1/10。

經過進一步的編譯器調查後,我注意到內層循環不再展開和向量化了。因此,我添加了以下#pragmas:

#pragma simd 
#pragma unroll 

矢量化工作正常,但其餘循環未展開。性能更好,但仍然只有正常版本的1/2左右。

這是正常的代碼的編譯器(-O3)輸出:

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 

而這裏填充一個與SIMD和展開編譯指示的輸出:

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 

於是展開得忽略。有沒有辦法強制它?我也問自己,如果多數民衆贊成在糟糕的表現的唯一原因..

編輯: 對於沒有填充的快速矩陣乘法的assambly看起來是這樣的:

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 

的一個緩慢的乘用填充看起來像這樣:

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 

這是我的解決方案的完整代碼。再說一次,如果我用n交換np,那麼性能會提高一倍以上。

#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

當你在至強phi上運行時,你是在一個核心上還是全部運行? – EOF

+0

122分散線程。我測試了不同的數字,但是所有這些數字都比較慢, – ImmaCute

+1

矩陣乘法與具有相同大小和對齊的memcpy()相比如何? – EOF

回答

1

的代碼片段你給太小,允許適當的測試我可以提出一個解決方案,而不正確測試它,所以它可能慘遭失敗。無論如何,這裏一無所獲。

你說你沒有填充數據來對齊行,但是AFAICS從來沒有將這些信息傳輸給編譯器,因此編譯器無法利用它。爲此,您有各種解決方案,但是因爲您已經有了用於並行化的OpenMP指令,所以使用一些更多的矢量化似乎是明顯的選擇。

這樣會給你這樣的事情:

#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]; 
     } 
    } 
} 

在這裏,我假設你的陣列是double,所以你可能需要改變的是他們沒有。除此之外,我認爲這個想法就在那裏。我不知道它是否適合你,但這絕對值得一試。

+0

謝謝你的建議。可悲的是,它並沒有做到這一點。如果有的話,我只會加快速度。我將完整的源代碼添加到了我的問題中。重要的想法要注意的是:第二個解決方案在Xeon Phi上只是比較慢,而不是在普通的Xeon處理器上。 – ImmaCute

+0

好吧,我沒有Phi來測試,所以我不能自己調整這個東西。然而,只有幾句話:'pn'是一個相當複雜的宏。你不能至少爲了測試的目的,用它的值5904取代它(就像你爲'n'做的那樣)? 2 /在你的代碼中,對齊的重要部分是關於行,而不是整個數組。這就是爲什麼我在我提出的解決方案中引入'aa''bb'和'cc'陣列的原因。你確實使用它? – Gilles

+0

現在,我做了,它給了我一個很大的加速。但我還不明白,爲什麼使用指針更快。你有解釋嗎? – ImmaCute