2016-12-13 13 views
0

MAESTRO 프로세서에서 OpenMP를 사용하는 일부 매트릭스 - 매트릭스 곱셈 벤치 마크 코드를 최적화하려고합니다. MAESTRO는 7x7 구성으로 2 차원 배열로 배열 된 49 개의 프로세서를 가지고 있습니다. 각 코어에는 고유 한 L1 및 L2 캐시가 있습니다. 보드 레이아웃은 http://i.imgur.com/naCWTuK.png입니다.NUMA 아키텍처에서 다른 데이터 형식의 OpenMP 성능

내 주요 질문 : 다른 데이터 형식 (char 대 short 대 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 비트 정수 행렬 곱셈 기준 :

여기서 I는 수신 된베이스 코드의 행렬 곱셈 기준의 단편이다
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 아키텍처를 사용하는 프로세서를 사용한 첫 번째 경험이므로 아직 배우면서 '최적화'가 불투명합니다. 여하튼, I는 동일한 조건 (스레드의 개수 및 배열의 ​​크기) 모두와 함께 상기 코드의 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는 클러스터 당 7 개의 노드와 총 7 개의 클러스터를 의미하기 때문에이 칩이 7X7 NUMA라고 말하는 것은 잘못된 것입니다. 이 칩에는 4 개의 외장 컨트롤러 만 있습니다. – user3528438

+0

이 칩의 코어는 실제로 꽤 큰 L2 캐시를 가지고 있으므로 데이터 세트가 충분히 크지 않다면 더 작은 데이터 유형을 사용하면 전체 너비 유형으로의 변환과 전체 폭 유형에서의 변환에 많은 시간을 낭비하게됩니다. 데이터를 압착해도 성능이 향상되지 않으면 데이터를 처리 할 필요가 없다는 의미입니다. – user3528438

+0

매트릭스의 크기는 얼마입니까? btw이 내 도움이 당신 http://lemire.me/blog/2013/09/13/are-8-bit-or-16-bit-counters-faster-than-32-bit-counters/ – dreamcrash

답변

1

두 가지

입니다 32 비트 (사 btye) 데이터 유형 및 N 바이트 경계에 데이터 구조를 정렬 주어진 컴파일러 비교하여 8 비트 (한 btye) 데이터 유형입니다. 일반적으로 32 비트로 기본 설정되어있는 경우 일반적으로 4 바이트 경계라고 생각합니다. 정렬 경계를 강제로 지정하는 컴파일러 옵션이 있습니다.

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

(32) 표준에서 일어나는 어떤 마스킹 작업에 비해 다른 3 바이트가 정확한 값을 얻기 위해 오프 마스크해야하는 1 바이트 데이터 형식을 처리하기 위해 발생하는 추가 작업이있을 수 있습니다 비트 (또는 64 비트) 데이터 형식.

다른 하나는 프로세서 및 메모리 선호도와 주어진 코어에서 실행되는 병렬 OPENMP 코드가 해당 CPU 코어에 직접 연결되지 않은 메모리에서 데이터를 가져오고 쓰는지 여부입니다.그렇다면 멀리 떨어져있는 메모리에 도달하기 위해 가져 오는 모든 허브 (들)가 분명히 런타임에 증가를 일으킬 것입니다. 이것이 내가 익숙하지 않은 MAESTRO 유형의 시스템에 적용되는지 확실하지 않습니다. 하지만 내가 뭘 설명하는 인텔 빠른 경로 연결 (QPI)를 통해 연결된 최신 모델 인텔 4 - CPU 시스템에 있습니다. 예를 들어, CPU 0의 코어 0에서 실행중인 경우 CPU 3의 코어 N에 연결된 QPI를 통해 DRAM에 액세스하는 것보다 해당 CPU 코어에 가장 가까운 DRAM 모듈의 메모리에서 페치하는 것이 가장 빠를 것입니다. 다른 블레이드 또는 노드에서 DRAM에 액세스하는 등의 작업을 수행 할 수 있습니다. 친화력이 MPI로 처리 될 수 있다는 것을 알고 있으며, OPENMP와 함께있을 수도 있지만 어쩌면 좋지 않을 수도 있습니다. 당신은 "openmp cpu memory affinity"에 대한 연구를 시도 할 것입니다.