Почему #pragma omp simd значительно улучшает производительность только в -O2 под компилятором gcc?

Проверьте следующий код:

#include <stdio.h>
#include <omp.h>

#define ARRAY_SIZE  (1024)
float A[ARRAY_SIZE];
float B[ARRAY_SIZE];
float C[ARRAY_SIZE];

int main(void)
{   
    for (int i = 0; i < ARRAY_SIZE; i++)
    {
        A[i] = i * 2.3;
        B[i] = i + 4.6;
    }

    double start = omp_get_wtime();
    for (int loop = 0; loop < 1000000; loop++)
    {
        #pragma omp simd
        for (int i = 0; i < ARRAY_SIZE; i++)
        {
            C[i] = A[i] * B[i];
        }
    }
    double end = omp_get_wtime();
    printf("Work consumed %f seconds\n", end - start);
    return 0;
}

Соберите и запустите его на моей машине, он выводит:

$ gcc -fopenmp parallel.c
$ ./a.out
Work consumed 2.084107 seconds

Если я закомментирую "#pragma omp simd", соберите и запустите его снова:

$ gcc -fopenmp parallel.c
$ ./a.out
Work consumed 2.112724 seconds

Мы видим, что "#pragma omp simd" не дает большого прироста производительности. Но если я добавлю опцию -O2, без "#pragma omp simd":

$ gcc -O2 -fopenmp parallel.c
$ ./a.out
Work consumed 0.446662 seconds

С "#pragma omp simd":

$ gcc -O2 -fopenmp parallel.c
$ ./a.out
Work consumed 0.126799 seconds

Мы видим большое улучшение. Но если использовать -O3, без "#pragma omp simd":

$ gcc -O3 -fopenmp parallel.c
$ ./a.out
Work consumed 0.127563 seconds

с "#pragma omp simd":

$ gcc -O3 -fopenmp parallel.c
$ ./a.out
Work consumed 0.126727 seconds

Мы видим, что результаты снова схожи.

Почему «#pragma omp simd» требует значительного улучшения производительности только в -O2 под компилятором gcc?


person Nan Xiao    schedule 27.12.2017    source источник
comment
Похоже, что компилятор дополнительно оптимизирует ваш код при использовании O3 и, вероятно, воспользуется преимуществами simd-инструкций. Вы сравнивали получившиеся сборки?   -  person Harald    schedule 27.12.2017


Ответы (1)


Забудьте о времени с -O0, это пустая трата времени.

gcc -O3 пытается автоматически векторизовать все циклы, поэтому использование прагм OpenMP помогает вам только для циклов, которые в противном случае были бы автоматически векторизованы только с квалификаторами -ffast-math, restrict или другими препятствиями для корректности при всех возможных обстоятельствах, которые компилятор должен удовлетворить для автовекторизации. чистого C. (Видимо здесь нет препятствий: здесь не редукция, а у вас чисто вертикальные операции. И вы работаете со статическими массивами, чтобы компилятор видел, что они не пересекаются)

gcc -O2 не включает -ftree-vectorize, поэтому вы получаете автоматическую векторизацию только в том случае, если используете прагмы OpenMP, чтобы запрашивать ее в определенных циклах.


Обратите внимание, что clang включает автоматическую векторизацию в -O2.


Стратегии автоматической векторизации GCC могут различаться между OpenMP и vanilla. IIRC, для циклов OpenMP gcc может просто использовать невыровненные загрузки/сохранения вместо скалярного до достижения границы выравнивания. Это не имеет недостатков производительности с AVX, если данные выравниваются во время выполнения, даже если этот факт не был известен во время компиляции. Это экономит много кода по сравнению с массивным полностью развернутым кодом запуска / очистки gcc.

Имеет смысл, если вы запрашиваете векторизацию SIMD с OpenMP, вы, вероятно, выровняли свои данные, чтобы избежать разделения строк кэша. Но в C не очень удобно учитывать тот факт, что указатель на float имеет большее выравнивание, чем ширина float. (Особенно то, что оно обычно имеет это свойство, даже если вам нужно, чтобы функция по-прежнему работала в тех редких случаях, когда это не так).

person Peter Cordes    schedule 27.12.2017
comment
gcc отводит прагме simd меньшую роль, чем другие компиляторы. Это все равно будет иметь значение (при -O2/3) в случае, когда вы опускаете требуемый в противном случае квалификатор ограничения. - person tim18; 27.12.2017
comment
@ tim18: да, хороший момент, это часть или что-то, что относится и к целочисленному коду. - person Peter Cordes; 27.12.2017
comment
Я хочу, чтобы GCC и Clang по умолчанию были как минимум -O2 такими же, как ICC. Я даже не вижу смысла в -O0 or -O1. Несколько раз, когда я использовал отладчик, проблема все равно появлялась только в оптимизированном коде. Я давно хотел спросить об этом, но какой смысл в -O1. - person Z boson; 28.12.2017
comment
Я обнаружил случай, когда использование OpenMP приводит к тому, что GCC плохо векторизует код. Код находится здесь. С Clang проблем нет, но чтобы получить наилучшие результаты с GCC, мне пришлось переместить функцию kernel в отдельный объектный файл (скомпилированный без -fopenmp). Так что иногда OpenMP может сделать оптимизацию хуже. - person Z boson; 28.12.2017
comment
По крайней мере, pragma omp simd полезен, чтобы избежать использования подсказки выравнивания, зависящей от компилятора, когда выравнивание строже, чем тип данных. - person Jorge Bellon; 25.01.2018