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



我正在尝试优化一些在Maestro处理器上使用OpenMP的矩阵乘数基准代码。大师在7x7配置中以二维阵列排列的49个处理器。每个核心都有自己的L1和L2缓存。董事会的布局可以在这里看到:https://i.stack.imgur.com/rg0fc.png。

我的主要问题是:不同的数据类型(char vs vs vs vs vs int等)可以直接影响基于数字的处理器上的OpenMP代码的性能吗?如果是这样,有没有办法减轻它?以下是我对为什么要问这个的解释。

我获得了一组研究小组使用的基准测量,以衡量给定处理器的性能。基准测试导致其他处理器的性能提高,但它们遇到了在大师上运行结果时没有看到相同类型的结果的问题。这是我收到的基本代码的矩阵乘法基准的片段:

来自标头文件的相关宏(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运行它更好。我的任务是优化大师的基准,以获得更好的性能。使用以下代码,我能够提高性能:

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位版本更快执行吗?这种差异的原因是什么,还有什么可能减轻它的原因?它可能与我正在使用的数组的数据类型有关还是其他?

想到的两件事是

您的8位(一个BTYE)数据类型与32位(四个BTYE)数据类型以及给定的编译器对齐数据结构与N字节边界的一致。我认为这通常是4个字节的边界,尤其是当默认为32位时。有一个编译器选项可以强制对齐边界。

为什么编译器在n个字节边界上对n个字节数据类型对齐?

可能会发生额外的操作来处理一个字节数据类型,其中必须掩盖其他3个字节才能获得正确的值,而没有标准的32位(或64--位)数据类型。

另一个是处理器和内存亲和力,以及在给定核心上运行的并行OpenMP代码是从内存中获取或编写直接连接到该CPU核心的数据。那么,无论枢纽的任何枢纽都必须经过以达到遥远的内存,显然会导致运行时间增加。我不确定这是否适用于您不熟悉的大师类型的系统;但是我要描述的是通过Intel QuickPath Connect(QPI)连接的晚期Model Intel 4-CPU系统。例如,如果您是在CPU 0的Core 0上运行,则从最接近该CPU Core的DRAM模块的内存中获取将是最快的,而不是访问CPI上的QPI,在CPU 3上连接到Core n,而不是通过某些Hub或Infiniband到在其他刀片或节点上访问DRAM,依此类推。我知道可以使用MPI来处理亲和力,而且我相信可以与OpenMP一起使用,但也许不那么。您可以尝试研究" OpenMP CPU内存亲和力"。

最新更新