Pragma omp для simd не генерирует векторные инструкции в GCC - PullRequest
0 голосов
/ 11 апреля 2020

Short : генерирует ли директива pragma omp for simd OpenMP код, который использует регистры SIMD?

Longer : как указано в документации OpenMP" Конструкция SIMD worksharing-l oop указывает, что итерации одного или нескольких связанных циклов будут распределены по уже существующим потокам [..] с использованием инструкций SIMD ". Исходя из этого утверждения, я ожидаю, что следующий код (simd. c) будет использовать регистры XMM, YMM или ZMM при компиляции под управлением gcc simd.c -o simd -fopenmp, но это не так.

#include <stdio.h>
#define N 100

int main() {
    int x[N];
    int y[N];
    int z[N];
    int i;
    int sum;

    for(i=0; i < N; i++) {
        x[i] = i;
        y[i] = i;
    }

    #pragma omp parallel
    {
        #pragma omp for simd
        for(i=0; i < N; i++) {
            z[i] = x[i] + y[i];
        }
        #pragma omp for simd reduction(+:sum)
        for(i=0; i < N; i++) {
            sum += x[i];
        }
    }
    printf("%d %d\n",z[N/2], sum);

    return 0;
}

При проверке сгенерированного ассемблера, работающего gcc simd.c -S -fopenmp регистр SIMD не используется.

Я могу использовать регистры SIMD без OpenMP, используя опцию -O3, потому что согласно G CC документации это включает в себя флаг -ftree-vectorize.

  • XMM регистры: gcc simd.c -o simd -O3
  • YMM регистры: gcc simd.c -o simd -O3 -march=skylake-avx512
  • ZMM регистры: gcc simd.c -o simd -O3 -march=skylake-avx512 -mprefer-vector-width=512

Однако использование флагов -march=skylake-avx512 -mprefer-vector-width=512 в сочетании с -fopenmp не генерирует SIMD-инструкции.

Таким образом, я могу легко векторизовать свой код с помощью -O3 без pragma omp for simd, но не наоборот.

На данный момент, моя цель - не генерировать SIMD-инструкции, а чтобы понять, как работают директивы OpenMP SIMD в G CC и как генерировать инструкции SIMD только с OpenMP (без -O3).

1 Ответ

2 голосов
/ 11 апреля 2020

Включите по крайней мере -O2 для -fopenmp для работы и для производительности в целом

gcc simd.c -S -fopenmp

G CC ' По умолчанию установлено значение -O0, анти-оптимизировано для согласованной отладки . Он никогда не будет автоматически векторизоваться с -O0, потому что бессмысленно, когда каждое значение i из источника C должно существовать в памяти, и так далее. Почему clang производит неэффективный asm с -O0 (для этой простой суммы с плавающей запятой)?

Также невозможно, когда вам нужно иметь возможность пошагово выравнивать строки источника по одной за раз, и даже измените i или содержимое памяти во время выполнения с помощью отладчика, и программа продолжит работать так, как вы ожидали бы, если бы абстрактная машина C.

Сборка без любая оптимизация является полным мусором для производительности; безумно даже думать, достаточно ли вы заботитесь о производительности, чтобы использовать OpenMP. (За исключением, конечно, фактической отладки.) Часто ускорение от антиоптимизированного до оптимизированного скаляра больше, чем вы могли бы получить от векторизации этого скалярного кода. , но оба могут быть важными факторами, так что вам определенно нужны оптимизации помимо авто-векторизации.


Я могу использовать SIMD-регистры без OpenMP, используя опцию -O3, потому что согласно документации G CC он включает флаг -ftree-vectorize.

Верно, так и сделайте. -O3 -march=native -flto обычно является лучшим выбором для кода, который будет выполняться на хосте компиляции. Кроме того, -fno-trapping-math -fno-math-errno должен быть безопасен для всего и включать некоторые лучшие функции FP, даже если вы не хотите -ffast-math. Также предпочтительно -fprofile-generate / -fprofile-use оптимизация по профилю (P GO), чтобы развернуть горячие петли и выбрать соответственно ветвление против ветвления, и т.д. c.

#pragma omp parallel все еще действует при -O3 -fopenmp - G CC по умолчанию не включает автопараллелизация.

Кроме того, #pragma omp simd иногда будет использовать другой стиль векторизации. В вашем случае кажется, что G CC забывает, что он знает, что массивы выровнены по 16 байтов, и использует movdqu нагрузки (когда AVX недоступен для невыровненного операнда источника памяти для paddd xmm0, [rax]). Сравните https://godbolt.org/z/8q8Dqm - вспомогательная функция main._omp_fn.0:, которую вызывает main, не предполагает выравнивания. (Хотя, может быть, после деления на число потоков это не удастся разбить массив на диапазоны, если G CC не потрудится делать куски векторного размера?)


Использовать -O2 -fopenmp чтобы получить то, что вы ожидали

OpenMP позволит g cc более просто и эффективно векторизовать циклы, в которых вы не использовали restrict для аргументов указателей на функции, чтобы они знали, что массивы не перекрываются или для плавающей запятой, чтобы он притворился, что математика FP ассоциативна, даже если вы не использовали -ffast-math.

Или если вы включили некоторую оптимизацию, но не полную оптимизацию (например, -O2, который не включает -ftree-vectorize), , тогда #pragma omp будет работать так, как вы ожидали.

Обратите внимание, что x[i] = y[i] = i; init l oop не получается автоматически векторизуется на -O2, но петли #pragma. И это без -fopenmp, чистый скаляр. Исследователь компилятора Godbolt


Серийный код -O3 будет работать быстрее для этого небольшого N, поскольку накладные расходы на запуск потока совсем не стоят этого. Но для больших N распараллеливание может помочь, если одно ядро ​​не может насытить пропускную способность памяти (например, на Xeon, но большинство двухъядерных / четырехъядерных процессоров для настольных ПК могут почти насытить mem пропускную способность одним ядром). Или, если ваши массивы в горячем кеше на разных ядрах.

К сожалению (?), Даже G CC -O3 не удается выполнить постоянное распространение по всему вашему коду и просто напечатать результат. Или объединить z[i] = x[i]+y[i] l oop с sum(x[]) l oop.

...