Я реализовал простое умножение матрицы 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);
}
Когда вы запускаете Xeon phi, вы работаете на одном ядре или все? – EOF
122 разбросанные темы. Я тестировал разные номера, но со всеми из них вторая реализация медленнее. – ImmaCute
Как умножается матрица на «memcpy()» того же размера и выравнивания? – EOF