Да, современные компиляторы автоматически векторизуются с помощью SSE2, если вы компилируете с полной оптимизацией.clang включает его даже при -O2, gcc при -O3.
Даже при -O1 или -Os компиляторы будут использовать инструкции загрузки / сохранения SIMD для копирования или инициализации структур или других объектов, которые шире, чем целочисленный регистр.Это на самом деле не считается авто-векторизацией;это больше похоже на встроенную стратегию memset / memcpy по умолчанию для небольших блоков фиксированного размера.Но он использует преимущества SIMD-инструкций и требует их поддержки.
SSE2 является базовым / необязательным для x86-64, поэтому компиляторы всегда могут использовать инструкции SSE1 / SSE2 при нацеливании на x86-64 .Более поздние наборы инструкций (SSE4, AVX, AVX2, AVX512 и не-SIMD-расширения, такие как BMI2, popcnt и т. Д.) Должны быть включены вручную, чтобы сообщить компилятору, что можно создавать код, который не будет работать на старых процессорах.Или заставить его генерировать несколько версий кода и выбирать во время выполнения, но это имеет дополнительные издержки и стоит только для больших функций.
-msse -msse2 -mfpmath=sse
уже по умолчанию для x86-64 , но не для 32-битного i386.Некоторые 32-битные соглашения о вызовах возвращают значения FP в регистрах x87, поэтому может быть неудобно использовать SSE / SSE2 для вычислений, а затем сохранять / перезагружать результат, чтобы получить его в x87 st(0)
.С -mfpmath=sse
, более умные компиляторы могут по-прежнему использовать x87 для вычисления, которое возвращает возвращаемое значение FP.
На 32-битном x86 -msse2
может не включаться по умолчанию, это зависит от того, как работал ваш компиляторсконфигурировано.Если вы используете 32-разрядные версии, потому что вы нацелены на старые процессоры, которые не могут выполнить 64-разрядный код, вы можете убедиться, что он отключен, или только -msse
.
Лучший способ настроить двоичный файл для процессора, на котором вы компилируете, - -O3 -march=native -mfpmath=sse
и использовать оптимизацию по времени соединения + оптимизацию по профилю .(gcc -fprofile-generate
/ запустить на некоторых тестовых данных / gcc -fprofile-use
).
Использование -march=native
создает двоичные файлы, которые могут не работать на более ранних процессорах, если компилятор решит использовать новые инструкции.Оптимизация по профилю очень полезна для gcc: она никогда не разворачивает циклы без нее.Но с PGO он знает, какие циклы выполняются часто / для большого количества итераций, т.е. какие циклы «горячие» и на которые стоит потратить больше кода.Оптимизация времени соединения позволяет встраивать / постоянное распространение по файлам. очень полезно, если у вас есть C ++ с множеством небольших функций, которые вы на самом деле не определяете в заголовочных файлах.
См. Как удалить ""шум" из вывода сборки GCC / clang? для получения дополнительной информации о просмотре вывода компилятора и его понимании.
Вот несколько конкретных примеров в проводнике компилятора Godbolt для x86-64 .У Godbolt также есть gcc для нескольких других архитектур, и с помощью clang вы можете добавить -target mips
или что-то еще, так что вы также можете увидеть автоматическую векторизацию для ARM NEON с правильными опциями компилятора, чтобы включить его.Вы можете использовать -m32
с компиляторами x86-64 для получения 32-битного кода поколения.
int sumint(int *arr) {
int sum = 0;
for (int i=0 ; i<2048 ; i++){
sum += arr[i];
}
return sum;
}
внутренний цикл с gcc8.1 -O3
(без -march=haswell
или с чем-либо для включения AVX / AVX2):
.L2: # do {
movdqu xmm2, XMMWORD PTR [rdi] # load 16 bytes
add rdi, 16
paddd xmm0, xmm2 # packed add of 4 x 32-bit integers
cmp rax, rdi
jne .L2 # } while(p != endp)
# then horizontal add and extract a single 32-bit sum
Без -ffast-math
компиляторы не могут переупорядочивать операции FP, поэтому эквивалент float
не выполняет векторизацию автоматически (см. Ссылку Godbolt: вы получаете скаляр addss
).(OpenMP может включить его для каждого цикла или использовать -ffast-math
).
Но некоторые элементы FP могут автоматически векторизоваться без изменения порядка операций.
// clang won't contract this into an FMA without -ffast-math :/
// but gcc will (if you compile with -march=haswell)
void scale_array(float *arr) {
for (int i=0 ; i<2048 ; i++){
arr[i] = arr[i] * 2.1f + 1.234f;
}
}
# load constants: xmm2 = {2.1, 2.1, 2.1, 2.1}
# xmm1 = (1.23, 1.23, 1.23, 1.23}
.L9: # gcc8.1 -O3 # do {
movups xmm0, XMMWORD PTR [rdi] # load unaligned packed floats
add rdi, 16
mulps xmm0, xmm2 # multiply Packed Single-precision
addps xmm0, xmm1 # add Packed Single-precision
movups XMMWORD PTR [rdi-16], xmm0 # store back to the array
cmp rax, rdi
jne .L9 # }while(p != endp)
множитель= 2.0f
приводит к удвоению addps
, сокращая пропускную способность в 2 раза на Haswell / Broadwell!Потому что до SKL FP add работает только на одном порту выполнения, но есть два модуля FMA, которые могут выполнять умножения.SKL сбросил сумматор и запустил добавление с теми же 2 на тактовую частоту и задержку, что и mul и FMA.(http://agner.org/optimize/, и другие ссылки на производительность смотрите в вики-теге x86 .)
Компиляция с -march=haswell
позволяет компилятору использовать один FMA для scale + add.(Но clang не будет сокращать выражение в FMA, если вы не используете -ffast-math
. IIRC есть возможность включить сжатие FP без других агрессивных операций.)