我正在尝试优化在MAESTRO处理器上使用OpenMP的一些矩阵 - 矩阵乘法基准代码。 MAESTRO有49个处理器,以7x7配置排列成二维阵列。每个核心都有自己的L1和L2缓存。在这里可以看到电路板的布局:http://i.imgur.com/naCWTuK.png。
我的主要问题是:不同的数据类型(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架构的处理器,因此我的优化和#39;因为我还在学习,所以很轻松。无论如何,我尝试使用相同的优化与上述代码的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 :(得分:1)
您的8位(一个btye)数据类型与32位(四个btye)数据类型以及给定的编译器将数据结构与N字节边界对齐。我认为它通常是4字节边界,特别是当它默认为32位时。有一个编译器选项可以强制对齐边界。
Why does compiler align N byte data types on N byte boundaries?
可能会发生额外的操作来处理单字节数据类型,其中必须屏蔽其他3个字节以获得正确的值,而不是使用标准32位(或64-)进行屏蔽操作bit)数据类型。
另一个是处理器和内存亲和性,以及在给定内核上运行的并行OPENMP代码是从未直接连接到该cpu内核的内存中获取还是写入数据。然后,无论哪个集线器都必须经过以获得远距离的存储器,这显然会导致运行时间的增加。我不确定这是否适用于我不熟悉的MAESTRO系统类型;但我所描述的是最新型的INTEL 4-cpu系统,它们通过英特尔快速通道连接(QPI)连接。例如,如果您在cpu 0的核心0上运行,那么从最接近该cpu核心的DRAM模块的内存中获取将是最快的,而不是通过连接到CPU 3上的核心N的QPI访问DRAM,而不是通过某个集线器或infiniband来访问访问某些其他刀片或节点上的DRAM,依此类推。 我知道MPI可以处理亲和力,我相信它可以与OPENMP一起使用,但可能不是很好。您可能会尝试研究“openmp cpu memory affinity”。