Я изучаю эффект векторизации на производительность программы. В связи с этим я написал следующий код:
#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 ГГц (двухъядерное)