2016-12-13 81 views
0

我正在嘗試優化某些在MAESTRO處理器上使用OpenMP的矩陣 - 矩陣乘法基準測試代碼。 MAESTRO有49個處理器以7x7配置排列成二維陣列。每個內核都有自己的L1和L2緩存。該板的佈局可以在這裏看到:http://i.imgur.com/naCWTuK.pngNUMA體系結構上不同數據類型的OpenMP性能

我的主要問題是:不同的數據類型(char vs short vs int等)會直接影響基於NUMA的處理器上的OpenMP代碼的性能嗎?如果是這樣,有沒有辦法緩解呢?以下是我對這個問題的解釋。

我得到了一組研究小組用來衡量給定處理器性能的基準。基準測試導致了其他處理器的性能提升,但是他們遇到了在MAESTRO上運行它們時看不到相同類型結果的問題。下面是從我收到的基本碼的矩陣乘法的基準的一個片段:從頭文件

相關宏(MAESTRO是64位):

#include <stdio.h> 
#include <stdlib.h> 
#include <math.h> 
#include <time.h> 
#include <sys/time.h> 
#include <cblas.h> 
#include <omp.h> 

//set data types 
#ifdef ARCH64 
    //64-bit architectures 
    #define INT8_TYPE char 
    #define INT16_TYPE short 
    #define INT32_TYPE int 
    #define INT64_TYPE long 
#else 
    //32-bit architectures 
    #define INT8_TYPE char 
    #define INT16_TYPE short 
    #define INT32_TYPE long 
    #define INT64_TYPE long long 
#endif 
#define SPFP_TYPE float 
#define DPFP_TYPE double 

//setup timer 

//us resolution 
#define TIME_STRUCT struct timeval 
#define TIME_GET(time) gettimeofday((time),NULL) 
#define TIME_DOUBLE(time) (time).tv_sec+1E-6*(time).tv_usec 
#define TIME_RUNTIME(start,end) TIME_DOUBLE(end)-TIME_DOUBLE(start) 

//select random seed method 
#ifdef FIXED_SEED 
    //fixed 
    #define SEED 376134299 
#else 
    //based on system time 
    #define SEED time(NULL) 
#endif 

32位整數矩陣乘法基準:​​

double matrix_matrix_mult_int32(int size,int threads) 
{ 


//initialize index variables, random number generator, and timer 
    int i,j,k; 
    srand(SEED); 
    TIME_STRUCT start,end; 

//allocate memory for matrices 
INT32_TYPE *A=malloc(sizeof(INT32_TYPE)*(size*size)); 
INT32_TYPE *B=malloc(sizeof(INT32_TYPE)*(size*size)); 
INT64_TYPE *C=malloc(sizeof(INT64_TYPE)*(size*size)); 

//initialize input matrices to random numbers 
//initialize output matrix to zeros 
for(i=0;i<(size*size);i++) 
{ 
    A[i]=rand(); 
    B[i]=rand(); 
    C[i]=0; 
} 

//serial operation 
if(threads==1) 
{ 
    //start timer 
    TIME_GET(&start); 
    //computation 
    for(i=0;i<size;i++) 
    { 
     for(k=0;k<size;k++) 
     { 
      for(j=0;j<size;j++) 
      { 
       C[i*size+j]+=A[i*size+k]*B[k*size+j]; 
      } 
     } 
    } 
    //end timer 
    TIME_GET(&end); 
} 
//parallel operation 
else 
{ 
    //start timer 
    TIME_GET(&start); 
    //parallelize with OpenMP 
    #pragma omp parallel for num_threads(threads) private(i,j,k) 
    for(i=0;i<size;i++) 
    { 
     for(k=0;k<size;k++) 
     { 
      for(j=0;j<size;j++) 
      { 
       C[i*size+j]+=A[i*size+k]*B[k*size+j]; 
      } 
     } 
    } 
    //end timer 
    TIME_GET(&end); 
} 

//free memory 
free(C); 
free(B); 
free(A); 

//compute and return runtime 
return TIME_RUNTIME(start,end); 
} 

連續運行上述基準比使用OpenMP運行性能更好。我的任務是優化MAESTRO的基準以獲得更好的性能。使用下面的代碼,我能得到的性能提升:

double matrix_matrix_mult_int32(int size,int threads) 
{ 

//initialize index variables, random number generator, and timer 
    int i,j,k; 
    srand(SEED); 
    TIME_STRUCT start,end; 


    //allocate memory for matrices 
    alloc_attr_t attrA = ALLOC_INIT; 
    alloc_attr_t attrB = ALLOC_INIT; 
    alloc_attr_t attrC = ALLOC_INIT; 

    alloc_set_home(&attrA, ALLOC_HOME_INCOHERENT); 
    alloc_set_home(&attrB, ALLOC_HOME_INCOHERENT); 
    alloc_set_home(&attrC, ALLOC_HOME_TASK); 

    INT32_TYPE *A=alloc_map(&attrA, sizeof(INT32_TYPE)*(size*size)); 
    INT32_TYPE *B=alloc_map(&attrB, sizeof(INT32_TYPE)*(size*size)); 
    INT64_TYPE *C=alloc_map(&attrC, sizeof(INT64_TYPE)*(size*size)); 

    #pragma omp parallel for num_threads(threads) private(i) 
    for(i=0;i<(size*size);i++) 
    { 

     A[i] = rand(); 
     B[i] = rand(); 
     C[i] = 0; 
     tmc_mem_flush(&A[i], sizeof(A[i])); 
     tmc_mem_flush(&B[i], sizeof(B[i])); 
     tmc_mem_inv(&A[i], sizeof(A[i])); 
     tmc_mem_inv(&B[i], sizeof(B[i])); 
    } 


    //serial operation 
    if(threads==1) 
    { 
     //start timer 
     TIME_GET(&start); 

     //computation 
     for(i=0;i<size;i++) 
     { 
      for(k=0;k<size;k++) 
      { 
       for(j=0;j<size;j++) 
       { 
        C[i*size+j]+=A[i*size+k]*B[k*size+j]; 
       } 
      } 
     } 

    TIME_GET(&end); 

    } 
    else 
    { 

     TIME_GET(&start); 

     #pragma omp parallel for num_threads(threads) private(i,j,k) schedule(dynamic) 
     for(i=0;i<size;i++) 
     { 
      for(j=0;j<size;j++) 
      { 
       for(k=0;k<size;k++) 
       { 
        C[i*size+j] +=A[i*size+k]*B[k*size+j]; 
       } 
      } 
     } 

     TIME_GET(&end); 
    } 


    alloc_unmap(C, sizeof(INT64_TYPE)*(size*size)); 
    alloc_unmap(B, sizeof(INT32_TYPE)*(size*size)); 
    alloc_unmap(A, sizeof(INT32_TYPE)*(size*size)); 


    //compute and return runtime 
    return TIME_RUNTIME(start,end); 
} 

製作語無倫次兩個輸入數組的緩存和使用OpenMP與動態調度幫助我得到的並行性能超越了串行性能。這是我第一次使用NUMA架構的處理器,所以我的「優化」很輕,因爲我還在學習。反正,我嘗試使用相同的優化與上面的代碼的8位整數版本與所有的在相同的條件(線程的數目和數組的大小):

double matrix_matrix_mult_int8(int size,int threads) 
{ 

//initialize index variables, random number generator, and timer 
    int i,j,k; 
    srand(SEED); 
    TIME_STRUCT start,end; 


    //allocate memory for matrices 
    alloc_attr_t attrA = ALLOC_INIT; 
    alloc_attr_t attrB = ALLOC_INIT; 
    alloc_attr_t attrC = ALLOC_INIT; 

    alloc_set_home(&attrA, ALLOC_HOME_INCOHERENT); 
    alloc_set_home(&attrB, ALLOC_HOME_INCOHERENT); 
    alloc_set_home(&attrC, ALLOC_HOME_TASK); 

    INT8_TYPE *A=alloc_map(&attrA, sizeof(INT8_TYPE)*(size*size)); 
    INT8_TYPE *B=alloc_map(&attrB, sizeof(INT8_TYPE)*(size*size)); 
    INT16_TYPE *C=alloc_map(&attrC, sizeof(INT16_TYPE)*(size*size)); 

    #pragma omp parallel for num_threads(threads) private(i) 
    for(i=0;i<(size*size);i++) 
    { 

     A[i] = rand(); 
     B[i] = rand(); 
     C[i] = 0; 
     tmc_mem_flush(&A[i], sizeof(A[i])); 
     tmc_mem_flush(&B[i], sizeof(B[i])); 
     tmc_mem_inv(&A[i], sizeof(A[i])); 
     tmc_mem_inv(&B[i], sizeof(B[i])); 
    } 


    //serial operation 
    if(threads==1) 
    { 
     //start timer 
     TIME_GET(&start); 

     //computation 
     for(i=0;i<size;i++) 
     { 
      for(k=0;k<size;k++) 
      { 
       for(j=0;j<size;j++) 
       { 
        C[i*size+j]+=A[i*size+k]*B[k*size+j]; 
       } 
      } 
     } 

    TIME_GET(&end); 

    } 
    else 
    { 

     TIME_GET(&start); 

     #pragma omp parallel for num_threads(threads) private(i,j,k) schedule(dynamic) 
     for(i=0;i<size;i++) 
     { 
      for(j=0;j<size;j++) 
      { 
       for(k=0;k<size;k++) 
       { 
        C[i*size+j] +=A[i*size+k]*B[k*size+j]; 
       } 
      } 
     } 

     TIME_GET(&end); 
    } 


    alloc_unmap(C, sizeof(INT16_TYPE)*(size*size)); 
    alloc_unmap(B, sizeof(INT8_TYPE)*(size*size)); 
    alloc_unmap(A, sizeof(INT8_TYPE)*(size*size)); 


    //compute and return runtime 
    return TIME_RUNTIME(start,end); 
} 

然而,8比特的OpenMP版本導致時間比32位OpenMP版本慢。 8位版本的執行速度不應該比32位版本快嗎?造成這種差異的原因是什麼?有哪些可能的事情可以緩解呢?它可能與我正在使用的數組的數據類型有關嗎?

+0

說這個芯片是7X7 NUMA是誤導的,因爲7x7 NUMA意味着每個羣集7個節點和7個羣集。該芯片顯然只有4個外部控制器。 – user3528438

+0

這個芯片中的內核實際上有一個相當大的二級緩存,所以如果你的數據集不夠大,那麼使用較小的數據類型會浪費很多時間進行全寬類型轉換。如果擠壓數據不會提高你的表現,那就意味着沒有必要這樣做。 – user3528438

+0

矩陣的大小是多少?順便說一句,這我幫你http://lemire.me/blog/2013/09/13/are-8-bit-or-16-bit-counters-faster-than-32-bit-counters/ – dreamcrash

回答

1

兩件事情,想到是

您的8位(一個btye)數據類型與一個32位(4個btye)數據類型和給定的編譯器對齊的數據結構以N字節邊界。我認爲它通常是4字節的邊界,特別是當它默認爲32位時。有一個編譯器選項來強制對齊邊界。

Why does compiler align N byte data types on N byte boundaries?

可能有額外的操作發生的事情來處理情況,其餘3個字節必須以獲得正確的值被屏蔽掉一個字節的數據類型,對無遮蔽操作與標準32發生位(或64位)數據類型。

另一個是處理器和內存關聯,以及在給定內核上運行的並行OPENMP代碼是否從未直接連接到該CPU內核的內存中獲取或寫入數據。然後,無論提取哪個中心,都需要經過以達到遠處的記憶,這顯然會導致運行時間的增加。我不確定這是否適用於我不熟悉的MAESTRO類型的系統;但我所描述的是通過英特爾快速路徑連接(QPI)連接的後期型號INTEL 4-CPU系統。例如,如果您在cpu 0的核心0上運行,則從與CPU c核心距離最近的DRAM模塊的內存中提取速度將最快,而不是通過連接到CPU 3上的核心N的QPI訪問DRAM,而不是通過某個集線器或infiniband到達訪問某些其他刀片或節點上的DRAM,等等。 我知道親和力可以用MPI來處理,我相信它可以與OPENMP一起使用,但可能不是那麼好。你可以嘗試研究「openmp cpu memory affinity」。