NUMA体系结构上不同数据类型的OpenMP性能

NUMA体系结构上不同数据类型的OpenMP性能

问题描述:

我正在尝试优化某些在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

两件事情,想到是

您的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”。