NUMA体系结构上不同数据类型的OpenMP性能
我正在尝试优化某些在MAESTRO处理器上使用OpenMP的矩阵 - 矩阵乘法基准测试代码。 MAESTRO有49个处理器以7x7配置排列成二维阵列。每个内核都有自己的L1和L2缓存。该板的布局可以在这里看到:http://i.imgur.com/naCWTuK.png。NUMA体系结构上不同数据类型的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位版本快吗?造成这种差异的原因是什么?有哪些可能的事情可以缓解呢?它可能与我正在使用的数组的数据类型有关吗?
两件事情,想到是
您的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”。
说这个芯片是7X7 NUMA是误导的,因为7x7 NUMA意味着每个群集7个节点和7个群集。该芯片显然只有4个外部控制器。 – user3528438
这个芯片中的内核实际上有一个相当大的二级缓存,所以如果你的数据集不够大,那么使用较小的数据类型会浪费很多时间进行全宽类型转换。如果挤压数据不会提高你的表现,那就意味着没有必要这样做。 – user3528438
矩阵的大小是多少?顺便说一句,这我帮你http://lemire.me/blog/2013/09/13/are-8-bit-or-16-bit-counters-faster-than-32-bit-counters/ – dreamcrash