Выберите реализацию сборки для использования на основе поддерживаемых инструкций - PullRequest
0 голосов
/ 28 ноября 2018

Я работаю над библиотекой C, которая компилирует / ссылается на файл .a, который пользователи могут статически связывать в своем коде.Производительность библиотеки очень важна, поэтому я пишу критические для производительности подпрограммы в сборке x86-64 для оптимизации производительности.

Для некоторых подпрограмм я могу получить значительно лучшую производительность, если использую инструкции BMI2, чем если я буду придерживаться«стандартный» набор команд x86-64.Проблема в том, что BMI2 появился совсем недавно, и некоторые из моих пользователей используют процессоры, которые не поддерживают эти инструкции.

Итак, я написал оптимизированные подпрограммы дважды , один раз с использованием инструкций BMI2 иодин раз, не используя их.В моей текущей установке я бы распространял две версии файла .a: «быстрая», которая требует поддержки инструкций BMI2, и «медленная», которая не требует поддержки инструкций BMI2.

Я спрашиваю, есть ли способ упростить это, распространяя один файл .a, который будет динамически выбирать правильную реализацию в зависимости от того, поддерживает ли процессор , на котором выполняется конечное приложение, , инструкции BMI2.

В отличие от похожих вопросов по StackOverflow, здесь есть две особенности:

  • Техника для выбора функции должна иметь особенно низкие издержки на критическом пути.Рассматриваемые подпрограммы после оптимизации сборки выполняются за ~ 10 нс, поэтому даже один оператор if может быть значительным.
  • Функция, которую необходимо выбрать «динамически», выбирается один раз в начале, а затем остается фиксированным на протяжении всей программы.Я надеюсь, что это предложит более быстрое решение, чем предложенное в этом вопросе: Выбор реализации метода во время выполнения

Самое быстрое решение, которое я придумал до сих порсделать следующее:

  1. Проверить, поддерживает ли процессор инструкции BMI2, с помощью инструкции cpuid.
  2. Установить глобальную переменную true или false в зависимости от результата.
  3. Ответвление значения этой глобальной переменной при каждом вызове функции.

Я не удовлетворен этим подходом, поскольку у него есть два недостатка:

  • Я не уверен, как можно автоматически запустить cpuid и установить глобальную переменную в начале программы, учитывая, что я распространяю файл .a и не имею контролянад функцией main в конечном двоичном файле. Я рад использовать C ++ здесь, если он предлагает лучшее решение, если окончательная библиотека все еще может быть связана с программой C и вызываться из нее.
  • Это приводит к дополнительным расходам каждый вызов функции, когда в идеале единственные издержки были бы при запуске программы.

Существуют ли какие-либо решения, которые более эффективны, чем те, которые я описал выше?

Ответы [ 2 ]

0 голосов
/ 27 декабря 2018

Если вы используете gcc, вы можете заставить компилятор автоматически реализовать весь код котельной пластины. Страница руководства gcc по функции multiversioning

0 голосов
/ 28 ноября 2018

x264 использует функцию init (которую пользователи библиотеки должны вызывать перед вызовом чего-либо еще, или что-то в этом роде) для установки структуры указателей функций на основе результатов CPUID.В том числе с учетом того, что pshufb медленно на некоторых ранних процессорах, которые его поддерживают.

Если ваши функции зависят от pdep / pext, вы, вероятно, захотите обнаружить AMD против Intel, потому что AMD pdep / pext очень медленный и, вероятно, не стоит использовать его на Райзене, даже если он доступен.(См. https://agner.org/optimize/ для таблиц инструкций.)


Указатели на функции довольно малы, примерно так же, как и вызов функции в разделяемой библиотеке или DLL.call [rel funcptr] вместо call func.(В сгенерированном компилятором asm, который вызывает ваши функции).

Код, зависящий от процессора: как избежать указателей на функции? показывает очень простой пример этого в C и запрашивает пути избегать этого.С помощью динамического связывания вы можете выполнять обнаружение ЦП во время динамического связывания, поэтому перенаправление динамического связывания также становится косвенным для вашей отправки ЦП (как это делает glibc для выбора оптимизированной реализации memcpy.)

Но со статическимсвязывая для .a, просто создайте указатели на функции, которые статически инициализируются для базовых версий, и ваша функция инициализации ЦП (которая, как мы надеемся, запускается до того, как любой из указателей на функции будет разыменована) переписывает их так, чтобы указывать на лучшую версию для текущего ЦП.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...