Почему векторизация цикла не имеет улучшения производительности

Я изучаю влияние векторизации на производительность программы. В связи с этим я написал следующий код:

#include 
#include 
#include 

#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, и здесь следует упомянуть еще несколько моментов. Во-первых, как упомянул @Vinska,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 байта). Что еще более важно, так как память, необходимая для,ab, а такжеc является смежным, prefetcher определенно очень поможет и загружает следующие блоки заранее. Сказав это, я думаю, что пропускная способность памяти, рассчитанная @Mysticial, слишком пессимистична.

Более того, использование SIMD для улучшения производительности программы для очень простого добавления упоминается вРуководство по векторизации Intel, Следовательно, похоже, что мы сможем добиться некоторого улучшения производительности для этого очень простого цикла.

Edit2: еще раз спасибо за ваши комментарии. Кроме того, благодаря примеру кода @Mysticial, я наконец-то увидел эффект SIMD на повышение производительности. Проблема, как упоминал Mysticial, заключалась в пропускной способности памяти. С выбором небольшого размера для,ab, а также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 ГГц (двухъядерный).

Ответы на вопрос(4)

Ваш ответ на вопрос