Использует ли компилятор инструкции SSE для обычного C-кода? - PullRequest
0 голосов
/ 10 июня 2018

Я вижу людей, использующих флаги -msse -msse2 -mfpmath=sse по умолчанию, в надежде, что это улучшит производительность.Я знаю, что SSE включается, когда в коде C используются специальные векторные типы.Но имеют ли эти флаги какое-то значение для обычного C-кода?Использует ли компилятор SSE для оптимизации обычного C-кода?

Ответы [ 2 ]

0 голосов
/ 10 июня 2018

Да, современные компиляторы автоматически векторизуются с помощью 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 без других агрессивных операций.)

0 голосов
/ 10 июня 2018

Невозможно ответить вообще.Однако для некоторого конкретного источника и компилятора C вы можете ответить на этот вопрос, посмотрев на сгенерированную сборку.Практически любой компилятор должен иметь возможность создавать файлы сборки.Затем вы можете выполнить поиск инструкций SSE.

Для большинства компиляторов Unix C используйте опцию -S.Подробнее Прочтите Руководство пользователя Fine вашего компилятора.

...