Создание обратно совместимых двоичных файлов с поддержкой новых инструкций процессора - PullRequest
3 голосов
/ 03 апреля 2020

Каков наилучший способ реализовать несколько версий одной и той же функции, которая использует заданные c инструкции ЦП, если они доступны (проверено во время выполнения), или, если нет, использует более медленную реализацию?

Например, x86 BMI2 предоставляет очень полезную инструкцию PDEP . Как бы я написал код C таким образом, чтобы он проверял наличие BMI2 исполняющего ЦП при запуске и использовал одну из двух реализаций - одну, которая использует вызов _pdep_u64 (доступный с -mbmi2), и другую, которая делает манипулирование битами "вручную" с использованием кода C. Есть ли встроенная поддержка для таких случаев? Как мне заставить G CC скомпилировать для более старой арки, обеспечивая при этом доступ к более новой intrinsi c? Я подозреваю, что выполнение выполняется быстрее, если функция вызывается через глобальный указатель на функцию, а не каждый раз if / else?

1 Ответ

4 голосов
/ 03 апреля 2020

Intel I CC имеет функцию automati c, которая отправляет для выбора оптимизированной версии для каждой архитектуры длиной go. Я не знаю деталей, но похоже, что это относится только к библиотекам Intel. Кроме того, он предназначен только для эффективной версии на процессорах Intel, поэтому будет нечестно по отношению к другим производителям . Для этого есть Dr. Блог Агнера Фога

Позже в G CC 4.8 была введена функция Функция Multiversioning . Он добавляет атрибут target, который вы объявляете в каждой версии вашей функции

__attribute__ ((target ("sse4.2")))
int foo() { return 1; }

__attribute__ ((target ("arch=atom")))
int foo() { return 2; }

int main() {
    int (*p)() = &foo;
    return foo() + p();
}

, который дублирует большой объем кода и является громоздким, поэтому G CC 6 добавил target_clones, который сообщает G CC для компиляции функции для нескольких клонов. Например, __attribute__((target_clones("avx2","arch=atom","default"))) void foo() {} создаст 3 разные версии foo. Более подробную информацию о них можно найти в документации G CC о атрибуте функции

Синтаксис был тогда принят Clang и I CC. Производительность может быть даже лучше, чем у глобального указателя на функцию, поскольку функциональные символы могут быть разрешены во время загрузки процесса вместо времени выполнения. Это одна из причин Intel Clear Linux работает так быстро . I CC может также создать несколько версий одного l oop во время автовекторизации

Вот пример из Пример с мультиверсионностью (Часть II) вместе с demo , который о popcnt, но вы поняли

__attribute__((target_clones("popcnt","default")))
int runPopcount64_builtin_multiarch_loop(const uint8_t* bitfield, int64_t size, int repeat) {
    int res = 0;
    const uint64_t* data = (const uint64_t*)bitfield;

    for (int r=0; r<repeat; r++)
    for (int i=0; i<size/8; i++) {
        res += popcount64_builtin_multiarch_loop(data[i]);
    }

    return res;
}

Если вы хотите сделать это вручную, вы можете сделать то же самое. Просто запустите функцию при запуске, чтобы вызвать cpuid, чтобы определить текущую архитектуру, затем указатель функции на правильную версию

Обратите внимание, что PDEP и PEXT очень медленны на текущей AMD Процессоры , поэтому они должны быть включены только на Intel

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