Включите по крайней мере -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.