Генерация кода для нескольких SIMD-архитектур
Я написал библиотеку, где я использую CMake для проверки наличия заголовков для MMX, SSE, SSE2, SSE4, AVX, AVX2 и AVX-512. В дополнение к этому я проверяю наличие инструкций и, если они есть, добавляю необходимые флаги компилятора, -msse2 -mavx -mfma и т. Д.
Это все очень хорошо, но я хотел бы развернуть один двоичный файл, который работает на разных поколениях процессоров.
Вопрос: можно ли сказать компилятору (GCC), что всякий раз, когда он оптимизирует функцию с использованием SIMD, он должен генерировать код для списка архитектур? И, конечно, ввести ветви высокого уровня
Я думаю, похоже на то, как компилятор генерирует код для функций, где указатели ввода выровнены либо на 4, либо на 8 байтов. Чтобы предотвратить это, я использую __builtin_assume_aligned
макро.
Что такое лучшая практика? Несколько бинарных файлов? Именование?
2 ответа
Пока вы не заботитесь о мобильности, да.
Последние версии GCC делают это проще, чем любой другой известный мне компилятор, используя атрибут функции target_clones. Просто добавьте атрибут со списком целей, для которых вы хотите создать версии, и GCC автоматически создаст различные варианты, а также функцию диспетчеризации для автоматического выбора версии во время выполнения.
Если вы хотите немного большей переносимости, вы можете использовать целевой атрибут, который также поддерживают clang и icc, но вам придется самостоятельно написать функцию диспетчеризации (что не сложно) и запустить ее несколько раз (обычно используя макрос или многократно включая заголовок).
AFAIK, если вы хотите, чтобы ваш код работал с MSVC, вам потребуется несколько вызовов компилятора с различными параметрами.
Если вы говорите только о том, чтобы заставить компилятор генерировать инструкции SSE/AVX и т. Д., И у вас есть код "общего назначения" (то есть вы явно не векторизуете использование встроенных функций или получили много кода, который компилятор обнаружит и auto-vectorise) тогда я должен предупредить вас, что AVX, AVX2 или AVX512, компилирующие всю вашу кодовую базу, вероятно, будут работать значительно медленнее, чем компиляция для версий SSE.
Когда опкоды AVX, использующие верхние половины регистров, обнаружены, ЦПУ включает верхнюю половину схемы (которая в противном случае отключается). Это потребляет больше энергии, генерирует больше тепла и снижает базовую тактовую частоту чипа, как правило, на 10-20% в зависимости от сочетания кодов высокой мощности и низкой мощности, поэтому вы сразу теряете, возможно, 15% производительности, а затем получаете делать довольно много векторизованной обработки, чтобы компенсировать этот дефицит производительности, прежде чем вы начнете видеть какой-либо выигрыш.
Смотрите мои более подробные объяснения и ссылки в этой теме.
Если, с другой стороны, вы явно векторизуете использование встроенных функций и уверены, что у вас достаточно большой пакет AVX и т. Д., Чтобы сделать его стоящим, я успешно написал код, в котором говорю MSVC компилировать для SSE2 (по умолчанию для x64), но затем я динамически проверяю возможности процессора и некоторые функции переключаются на путь кода, реализованный с использованием встроенных функций AVX.
MSVC допускает это (он будет выдавать предупреждения, но вы можете их отключить), но ту же технику трудно заставить работать в GCC 4.9, поскольку встроенные функции считаются объявленными компилятором только при использовании соответствующего флага генерации кода. [ОБНОВЛЕНИЕ: @nemequ ниже объясняет, как вы можете сделать это в gcc, используя атрибуты для декорирования функций] В зависимости от версии GCC вам может потребоваться компилировать файлы с разными флагами, чтобы получить работоспособную систему.
О, и вы должны также следить за переходами AVX-SSE (вызывая VZEROUPPER, когда вы выходите из раздела кода AVX, чтобы вернуться к коду SSE) - это можно сделать, но я обнаружил, что понимание последствий для ЦП было более серьезной битвой, чем я изначально предусматривается.