Я изучаю эффект векторизации на производительность программы. В связи с этим я написал следующий код:
#include <stdio.h>
#include <sys/time.h>
#include <stdlib.h>
#define LEN 10000000
int main(){
struct timeval stTime, endTime;
double* a = (double*)malloc(LEN*sizeof(*a));
double* b = (double*)malloc(LEN*sizeof(*b));
double* c = (double*)malloc(LEN*sizeof(*c));
int k;
for(k = 0; k < LEN; k++){
a[k] = rand();
b[k] = rand();
}
gettimeofday(&stTime, NULL);
for(k = 0; k < LEN; k++)
c[k] = a[k] * b[k];
gettimeofday(&endTime, NULL);
FILE* fh = fopen("dump", "w");
for(k = 0; k < LEN; k++)
fprintf(fh, "c[%d] = %f\t", k, c[k]);
fclose(fh);
double timeE = (double)(endTime.tv_usec + endTime.tv_sec*1000000 - stTime.tv_usec - stTime.tv_sec*1000000);
printf("Time elapsed: %f\n", timeE);
return 0;
}
В этом коде я просто инициализирую и умножая два вектора. Результаты сохраняются в векторе c. В основном меня интересует эффект векторизации следующего цикла:
for(k = 0; k < LEN; k++)
c[k] = a[k] * b[k];
Я скомпилирую код, используя следующие две команды:
1) icc -O2 TestSMID.c -o TestSMID -no-vec -no-simd
2) icc -O2 TestSMID.c -o TestSMID -vec-report2
Я ожидаю увидеть улучшение производительности, поскольку вторая команда успешно векторизовает цикл. Тем не менее, мои исследования показывают, что нет улучшения производительности, когда цикл векторизован.
Возможно, я что-то пропустил, так как я не очень хорошо знаком с этой темой. Поэтому, пожалуйста, дайте мне знать, если что-то не так с моим кодом.
Заранее благодарим за помощь.
PS: Я использую Mac OSX, поэтому нет необходимости выровнять данные, поскольку все выделенные ячейки памяти выравниваются по 16 байт.
Изменить:
Я хотел бы поблагодарить всех вас за ваши комментарии и ответы.
Я подумал о ответе, предложенном @Mysticial, и есть некоторые дополнительные моменты, которые следует упомянуть здесь.
Во-первых, как отметил В. Винска, c[k]=a[k]*b[k] не принимает только один цикл. В дополнение к приращению индекса цикла и сравнению для обеспечения того, чтобы k было меньше, чем LEN, для выполнения операции необходимо выполнить другие действия. Посмотрев на код сборки, сгенерированный компилятором, можно увидеть, что простое умножение требует гораздо больше одного цикла. Обозначенная версия выглядит так:
L_B1.9: # Preds L_B1.8
movq %r13, %rax #25.5
andq $15, %rax #25.5
testl %eax, %eax #25.5
je L_B1.12 # Prob 50% #25.5
# LOE rbx r12 r13 r14 r15 eax
L_B1.10: # Preds L_B1.9
testb $7, %al #25.5
jne L_B1.32 # Prob 10% #25.5
# LOE rbx r12 r13 r14 r15
L_B1.11: # Preds L_B1.10
movsd (%r14), %xmm0 #26.16
movl $1, %eax #25.5
mulsd (%r15), %xmm0 #26.23
movsd %xmm0, (%r13) #26.9
# LOE rbx r12 r13 r14 r15 eax
L_B1.12: # Preds L_B1.11 L_B1.9
movl %eax, %edx #25.5
movl %eax, %eax #26.23
negl %edx #25.5
andl $1, %edx #25.5
negl %edx #25.5
addl $10000000, %edx #25.5
lea (%r15,%rax,8), %rcx #26.23
testq $15, %rcx #25.5
je L_B1.16 # Prob 60% #25.5
# LOE rdx rbx r12 r13 r14 r15 eax
L_B1.13: # Preds L_B1.12
movl %eax, %eax #25.5
# LOE rax rdx rbx r12 r13 r14 r15
L_B1.14: # Preds L_B1.14 L_B1.13
movups (%r15,%rax,8), %xmm0 #26.23
movsd (%r14,%rax,8), %xmm1 #26.16
movhpd 8(%r14,%rax,8), %xmm1 #26.16
mulpd %xmm0, %xmm1 #26.23
movntpd %xmm1, (%r13,%rax,8) #26.9
addq $2, %rax #25.5
cmpq %rdx, %rax #25.5
jb L_B1.14 # Prob 99% #25.5
jmp L_B1.20 # Prob 100% #25.5
# LOE rax rdx rbx r12 r13 r14 r15
L_B1.16: # Preds L_B1.12
movl %eax, %eax #25.5
# LOE rax rdx rbx r12 r13 r14 r15
L_B1.17: # Preds L_B1.17 L_B1.16
movsd (%r14,%rax,8), %xmm0 #26.16
movhpd 8(%r14,%rax,8), %xmm0 #26.16
mulpd (%r15,%rax,8), %xmm0 #26.23
movntpd %xmm0, (%r13,%rax,8) #26.9
addq $2, %rax #25.5
cmpq %rdx, %rax #25.5
jb L_B1.17 # Prob 99% #25.5
# LOE rax rdx rbx r12 r13 r14 r15
L_B1.18: # Preds L_B1.17
mfence #25.5
# LOE rdx rbx r12 r13 r14 r15
L_B1.19: # Preds L_B1.18
mfence #25.5
# LOE rdx rbx r12 r13 r14 r15
L_B1.20: # Preds L_B1.14 L_B1.19 L_B1.32
cmpq $10000000, %rdx #25.5
jae L_B1.24 # Prob 0% #25.5
# LOE rdx rbx r12 r13 r14 r15
L_B1.22: # Preds L_B1.20 L_B1.22
movsd (%r14,%rdx,8), %xmm0 #26.16
mulsd (%r15,%rdx,8), %xmm0 #26.23
movsd %xmm0, (%r13,%rdx,8) #26.9
incq %rdx #25.5
cmpq $10000000, %rdx #25.5
jb L_B1.22 # Prob 99% #25.5
# LOE rdx rbx r12 r13 r14 r15
L_B1.24: # Preds L_B1.22 L_B1.20
И неглавная версия:
L_B1.9: # Preds L_B1.8
xorl %eax, %eax #25.5
# LOE rbx r12 r13 r14 r15 eax
L_B1.10: # Preds L_B1.10 L_B1.9
lea (%rax,%rax), %edx #26.9
incl %eax #25.5
cmpl $5000000, %eax #25.5
movsd (%r15,%rdx,8), %xmm0 #26.16
movsd 8(%r15,%rdx,8), %xmm1 #26.16
mulsd (%r13,%rdx,8), %xmm0 #26.23
mulsd 8(%r13,%rdx,8), %xmm1 #26.23
movsd %xmm0, (%rbx,%rdx,8) #26.9
movsd %xmm1, 8(%rbx,%rdx,8) #26.9
jb L_B1.10 # Prob 99% #25.5
# LOE rbx r12 r13 r14 r15 eax
Кроме того, процессор не загружает только 24 байта. В каждом доступе к памяти загружается полная строка (64 байта). Что еще более важно, так как память, необходимая для a, b и c, смежна, prefetcher определенно поможет много и загрузит следующие блоки заранее.
Сказав это, я думаю, что ширина памяти, рассчитанная @Mysticial, слишком пессимистична.
Кроме того, использование SIMD для улучшения производительности программы для очень простого добавления упоминается в Руководстве по векторизации Intel. Таким образом, кажется, что мы должны получить некоторое улучшение производительности для этого очень простого цикла.
Edit2:
Еще раз спасибо за ваши комментарии. Кроме того, спасибо @Mysticial пример кода, я наконец увидел эффект SIMD на повышение производительности. Проблема, как упоминалось в Mystical, была пропускная способность памяти. Выбрав небольшой размер для a, b и c, которые вписываются в кеш L1, можно видеть, что SIMD может значительно улучшить производительность. Вот результаты, которые я получил:
icc -O2 -o TestSMIDNoVec -no-vec TestSMID2.c: 17.34 sec
icc -O2 -o TestSMIDVecNoUnroll -vec-report2 TestSMID2.c: 9.33 sec
И разворачивание цикла улучшает производительность еще больше:
icc -O2 -o TestSMIDVecUnroll -vec-report2 TestSMID2.c -unroll=8: 8.6sec
Кроме того, я должен упомянуть, что для моего процессора требуется только один цикл для завершения итерации при компиляции с помощью -O2.
PS: Мой компьютер - это ядро Macbook Pro i5 @2.5 ГГц (двухъядерное)