2016-06-06 9 views
2

Я реализовал простое умножение матрицы n x n для тестирования одинаковых настроек производительности в c с помощью OpenMp. Мой исходный код выглядит следующим образом:Xeon Phi: более медленная производительность с дополнением

#pragma omp parallel for shared(a,b,c) private(h,i,j,k) 
    for(i = 0; i < n; i++) 
    { 
     for(j = 0; j < n; j++) 
     { 
      for(k = 0; k < n; k++) 
      { 
       a[i*n+j]+= b[i*n+k] * c[k*n+j]; 

Компилятор переключает J и K цикл, так что я получаю ikj-алгоритм. Первое, что я хотел реализовать, - это заполнить для каждой строки до 64 байт для доступа к кеш-керам. Поэтому я вычислил необходимый дополнительный размер для каждой строки. При переходе строки 5900 новый размер составляет 5904 (ссылка ISBN-9780124104143). Мой новый код:

#pragma omp parallel for shared(a,b,c) private(h,i,j,k) 
    for(i = 0; i < n; i++) 
    { 
     for(k = 0; k < n; k++) 
     { 
      #pragma simd 
      #pragma unroll(4) 
      for(j = 0; j < n; j++) 
      { 
       a[i*pn+j]+= b[i*pn+k] * c[k*pn+j]; 

где pn - новая, дополненная длина строки. Мне пришлось вручную переставлять мои циклы, потому что компилятор отказался. Запустив этот код на обычном процессоре Xeon, я получаю почти ту же производительность, что и раньше, может быть, немного лучше. То, что я ожидал. Но когда я запускаю код на Xeon Phi, он составляет около 1/10 от исходного кода.

После дальнейшего расследования компилятора я заметил, что внутренний цикл больше не разворачивается и не вектурируется. Поэтому я добавил следующие #pragmas:

#pragma simd 
#pragma unroll 

Векторизация прекрасно работает, но петля остаток не получает развернутый. Производительность намного лучше, но все же только около 1/2 от нормальной версии.

Вот компилятор (-O3) выход обычного кода:

LOOP BEGIN at mm_par.c(75,3) 
    remark #25444: Loopnest Interchanged: (1 2 3) --> (1 3 2) 

    LOOP BEGIN at mm_par.c(79,5) 
    remark #25460: No loop optimizations reported 

    LOOP BEGIN at mm_par.c(77,4) 
    remark #15301: PERMUTED LOOP WAS VECTORIZED 
    LOOP END 

    LOOP BEGIN at mm_par.c(77,4) 
    <Remainder> 
    remark #25436: completely unrolled by 4 
    LOOP END 
    LOOP END 
    LOOP END 

А вот выход проложенный один с SIMD и разворачивая прагмах:

LOOP BEGIN at mm_ali.c(76,3) 
remark #25460: No loop optimizations reported 

LOOP BEGIN at mm_ali.c(78,4) 
remark #25460: No loop optimizations reported 

    LOOP BEGIN at mm_ali.c(82,10) 
    remark #15301: SIMD LOOP WAS VECTORIZED 
    LOOP END 
LOOP END 
LOOP END 

Так разворачивания получает игнорируются. Есть ли способ заставить его? Я также вопрос сам, если то единственная причина плохой производительности ..

редактировать: Собрание для быстрого умножения матриц без заполнения выглядит следующим образом:

vmovapd c(%r15,%rbx,8), %zmm1       #81.28 c1 
    vprefetche1 2048+a(%r11,%rbx,8)       #81.6 c5 
    vmovapd 64+c(%r15,%rbx,8), %zmm3      #81.28 c9 
    vprefetch0 768+a(%r11,%rbx,8)       #81.6 c13 
    vmovapd 128+c(%r15,%rbx,8), %zmm4      #81.28 c17 
    vprefetch1 2048+c(%r15,%rbx,8)       #81.28 c21 
    vmovapd 192+c(%r15,%rbx,8), %zmm5      #81.28 c25 
    vprefetch0 768+c(%r15,%rbx,8)       #81.28 c29 
    vfmadd213pd a(%r11,%rbx,8), %zmm0, %zmm1    #81.6 c33 
    vprefetche1 2112+a(%r11,%rbx,8)       #81.6 c37 
    vfmadd213pd 64+a(%r11,%rbx,8), %zmm0, %zmm3    #81.6 c41 
    vprefetch0 832+a(%r11,%rbx,8)       #81.6 c45 
    vfmadd213pd 128+a(%r11,%rbx,8), %zmm0, %zmm4   #81.6 c49 
    vprefetch1 2112+c(%r15,%rbx,8)       #81.28 c53 
    vfmadd213pd 192+a(%r11,%rbx,8), %zmm0, %zmm5   #81.6 c57 
    vprefetch0 832+c(%r15,%rbx,8)       #81.28 c61 
    vmovaps %zmm1, a(%r11,%rbx,8)       #81.6 c65 
    vprefetche1 2176+a(%r11,%rbx,8)       #81.6 c69 
    vmovaps %zmm3, 64+a(%r11,%rbx,8)      #81.6 c73 
    vprefetch0 896+a(%r11,%rbx,8)       #81.6 c77 
    vmovaps %zmm4, 128+a(%r11,%rbx,8)      #81.6 c81 
    vprefetch1 2176+c(%r15,%rbx,8)       #81.28 c85 
    vmovaps %zmm5, 192+a(%r11,%rbx,8)      #81.6 c89 
    vprefetch0 896+c(%r15,%rbx,8)       #81.28 c93 
    vprefetche1 2240+a(%r11,%rbx,8)       #81.6 c97 
    vprefetch0 960+a(%r11,%rbx,8)       #81.6 c101 
    vprefetch1 2240+c(%r15,%rbx,8)       #81.28 c105 
    vprefetch0 960+c(%r15,%rbx,8)       #81.28 c109 
    addq  $32, %rbx          #77.4 c113 
    cmpq  %rsi, %rbx         #77.4 c117 
    jb  ..B1.51  # Prob 99%      #77.4 c117 

Один для медленного умножения padding выглядит следующим образом:

vloadunpackld (%rbx), %zmm0        #83.6 c1 
    addl  $32, %r15d         #81.10 c1 
    vprefetch1 2048+c(%rcx)         #83.30 c5 
    vloadunpackhd 64(%rbx), %zmm0       #83.6 c9 
    addq  $256, %rbx         #81.10 c9 
    vprefetch0 512+c(%rcx)         #83.30 c13 
    vbroadcastsd b(%r12,%r13,8), %zmm2      #83.18 c17 
    vprefetch1 2112+c(%rcx)         #83.30 c21 
    vfmadd132pd c(%rcx), %zmm0, %zmm2      #83.6 c25 
    vprefetch0 576+c(%rcx)         #83.30 c29 
    vpackstoreld %zmm2, (%rsi)        #83.6 c33 
    vprefetch1 2176+c(%rcx)         #83.30 c37 
    vpackstorehd %zmm2, 64(%rsi)       #83.6 c41 
    addq  $256, %rsi         #81.10 c41 
    vprefetch0 640+c(%rcx)         #83.30 c45 
    vloadunpackld (%rdi), %zmm3        #83.6 c49 
    vprefetch1 2240+c(%rcx)         #83.30 c53 
    vloadunpackhd 64(%rdi), %zmm3       #83.6 c57 
    addq  $256, %rdi         #81.10 c57 
    vprefetch0 704+c(%rcx)         #83.30 c61 
    vbroadcastsd b(%r12,%r13,8), %zmm4      #83.18 c65 
    vfmadd132pd 64+c(%rcx), %zmm3, %zmm4     #83.6 c69 
    nop              #83.6 c73 
    vpackstoreld %zmm4, (%r8)        #83.6 c77 
    vpackstorehd %zmm4, 64(%r8)        #83.6 c81 
    addq  $256, %r8          #81.10 c81 
    vloadunpackld (%r9), %zmm5        #83.6 c85 
    vloadunpackhd 64(%r9), %zmm5       #83.6 c89 
    addq  $256, %r9          #81.10 c89 
    vbroadcastsd b(%r12,%r13,8), %zmm6      #83.18 c93 
    vfmadd132pd 128+c(%rcx), %zmm5, %zmm6     #83.6 c97 
    nop              #83.6 c101 
    vpackstoreld %zmm6, (%r10)        #83.6 c105 
    vpackstorehd %zmm6, 64(%r10)       #83.6 c109 
    addq  $256, %r10         #81.10 c109 
    vloadunpackld (%r11), %zmm7        #83.6 c113 
    vloadunpackhd 64(%r11), %zmm7       #83.6 c117 
    addq  $256, %r11         #81.10 c117 
    vbroadcastsd b(%r12,%r13,8), %zmm8      #83.18 c121 
    vfmadd132pd 192+c(%rcx), %zmm7, %zmm8     #83.6 c125 
    addq  $256, %rcx         #81.10 c129 
    movb  %al, %al          #83.6 c129 
    vpackstoreld %zmm8, (%rdx)        #83.6 c133 
    vpackstorehd %zmm8, 64(%rdx)       #83.6 c137 
    addq  $256, %rdx         #81.10 c137 
    cmpl  $5888, %r15d         #81.10 c141 
    jb  ..B1.42  # Prob 99%      #81.10 c141 

Полный текст моего решения. Опять же, если я обмениваю np с n, производительность будет более чем в два раза быстрее.

#include <sys/time.h> 
#include <omp.h> 

#ifndef max 
#define max(a,b) (((a) (b)) ? (a) : (b)) 
#define min(a,b) (((a) < (b)) ? (a) : (b)) 
#endif 

#define n 5900 
#define pn ((((n*sizeof(double))+63)/64)*(64/sizeof(double))) //padding 
#define threadNum 144 
#define loops 1 

double dtime() 
{ 
    double tseconds = 0.0; 
    struct timeval mytime; 
    gettimeofday(&mytime, (struct timezone*)0); 
    tseconds = (double)(mytime.tv_sec + mytime.tv_usec*1.0e-6); 
    return tseconds; 

} 

double a[n*pn] __attribute__((aligned(64))); 
double b[n*pn] __attribute__((aligned(64))); 
double c[n*pn] __attribute__((aligned(64))); 


main(int argc, char **argv){ 

    int threadNumber, loopNumber; 
    if(argc == 3) 
    { 
     threadNumber = atoi(argv[1]); 
     loopNumber = atoi(argv[2]); 
    } else 
    { 
     threadNumber = threadNum; 
     loopNumber = loops;  
    } 


    double tstart, tstop, ttime; 
    int i,j,k,h; 

    // initialize matrices 
    #pragma omp parallel for 
    for(i = 0; i < pn*n; i++) 
    { 
     a[i]=0.0; 
     b[i]=2.0; 
     c[i]=2.0; 
    } 

    omp_set_num_threads(threadNumber); 

    tstart = dtime(); 

    //parallelize via OpenMP on MIC 
    for(h = 0; h < loopNumber; h++){ 
     #pragma omp parallel for shared(a,b,c) private(h,i,j,k) 
     for(i = 0; i < n; i++) 
     { 
      for(k = 0; k < n; k++) 
      {   
       #pragma omp simd aligned(a, b, c: 64) 
       for(j = 0; j < n; j++)    
       { 
        a[i*pn+j]+= b[i*pn+k] * c[k*pn +j]; 
       } 
      } 
     } 

    } 

    tstop = dtime(); 
    double elapsed = tstop - tstart; 

    double mFlops = ((double)n)*n*n*2.0*loopNumber/elapsed*1.0e-06; 

    #pragma omp parallel 
    #pragma omp master 
    printf("%d %.3f\n", omp_get_num_threads(), mFlops); 

} 
+0

Когда вы запускаете Xeon phi, вы работаете на одном ядре или все? – EOF

+0

122 разбросанные темы. Я тестировал разные номера, но со всеми из них вторая реализация медленнее. – ImmaCute

+1

Как умножается матрица на «memcpy()» того же размера и выравнивания? – EOF

ответ

1

Фрагмент кода, вы дали слишком мало, чтобы обеспечить надлежащее тестирование, я могу предложить на решение без проверки его должным образом, так что он может с треском провалились. Во всяком случае, здесь ничего не происходит.

Вы сказали, что вы поместили данные для выравнивания строк, но AFAICS эта информация никогда не передается компилятору, что поэтому не может ее использовать. Для этого у вас есть различные решения, но поскольку у вас есть директивы OpenMP уже для параллелизации, использование еще одного для векторизации кажется очевидным выбором.

Так что даст вам что-то вроде этого:

#pragma omp parallel for private(i, j, k) 
for(i = 0; i < n; i++) { 
    double *aa = &a[i * pn]; 
    double *bb = &b[i * pn]; 
    for(k = 0; k < n; k++) { 
     double *cc = &c[k * pn]; 
     #pragma omp simd aligned(aa, bb, cc: 64) 
     for(j = 0; j < n; j++) { 
      aa[j] += bb[k] * cc[j]; 
     } 
    } 
} 

Здесь я предположил, ваши массивы были double так что вам, возможно, потребуется изменить что они не были. Кроме того, я думаю, что идея есть. Я не знаю, будет ли это работать для вас, но это определенно стоит попробовать.

+0

Благодарим вас за предложение. К сожалению, это не делает трюк. Я немного ускоряюсь, если вообще есть. Я добавил полный исходный код к моему вопросу. Важно отметить следующее: второе решение работает только медленнее на Xeon Phi, а не на обычном процессоре Xeon. – ImmaCute

+0

Хорошо, у меня нет Phi для тестирования, поэтому я не могу настроить эту вещь самостоятельно. Однако, всего несколько замечаний: 'pn' - довольно запутанный макрос. Не можете ли вы хотя бы для тестирования заменить его значением 5904 (как и для 'n')? 2/в вашем коде важная часть выравнивания относится к строкам, а не к полному массиву. Вот почему я представил в предлагаемом решении массивы 'aa'' bb' и 'cc'. Вы действительно его использовали? – Gilles

+0

Теперь, я сделал, и это дает мне отличное ускорение. Но я пока не понимаю, почему использование указателей происходит быстрее. У вас есть объяснение? – ImmaCute