Нужно ли создавать несколько исполняемых файлов для целевых наборов команд?
Предположим, у меня есть программа для выполнения операций AES.
Некоторые продвинутые процессоры имеют набор команд AES-NI, а другие - нет.
Должен ли я скомпилировать мою программу в два исполняемых файла: A_with_aes_ni.exe и B_without_aes_ni.exe?
2 ответа
То, что вы хотите, называется диспетчером ЦП. Агнер Фог имеет 10 страниц текста по этому вопросу в третьей главе "Создание критического кода в нескольких версиях для разных наборов инструкций" своего руководства по оптимизации C++. Он обсуждает это как с GCC, так и с ICC.
Вам нужен только один исполняемый файл, но вам нужно скомпилировать два разных объектных файла с включенной AES и без нее. Затем диспетчер определяет, какой набор инструкций доступен, и выбирает путь кода на основании этого.
Я пытался сделать это с диспетчером процессора MSVC2010 для Visual Studio для AVX и SSE, но безуспешно. Я подозреваю, что мог бы заставить это работать теперь все же.
Редактировать: в векторном классе Агнера Фога у него есть файл dispatch_example.cpp
а также instrset_detech.cpp
который должен иметь большую часть того, что нужно сделать диспетчеру. Вам все еще нужно выяснить, как определить, есть ли у процессора AES. Вам нужно увеличить файл intrset_detect.cpp. Согласно википедии, когда вы читаете CPUID, бит 23 в регистре ECX устанавливается, если у CPU есть AES. В Википедии также есть примеры кода для чтения CPUID (помимо instrset_detech.cpp
- еще один хороший пример на https://github.com/Mysticial/Flops в файле cpuid.c)
Один из способов сделать это в Solaris - это иметь библиотеки аппаратных возможностей, которые динамически загружаются компоновщиком во время выполнения.
Другой вариант - сначала загрузить обработчик прерываний для недопустимых инструкций, а затем проверить нужные инструкции на машинном языке. Если вы попали в ловушку, то вы знаете, что не можете использовать оптимизированную версию и должны загружать неоптимизированную (или менее оптимизированную).
Хотя мне нравится предложение Эндрю выше, я думаю, что безопаснее проверить конкретные инструкции, которые вам нужны. Таким образом, вам не нужно постоянно обновлять приложение для более новых выходных данных CPUID.
Отредактировано, чтобы добавить: я понимаю, что должен был привести пример. Для libc Solaris на платформе x64 мы предоставляем hw-оптимизированные версии библиотеки - три для 32-битных, одна для 64-битных. Мы можем увидеть различия, запустив elfdump -H
на интересующий файл:
s11u1:jmcp $ elfdump -H /usr/lib/libc/libc_hwcap1.so.1
Capabilities Section: .SUNW_cap
Object Capabilities:
index tag value
[0] CA_SUNW_HW_1 0x86d [ SSE MMX CMOV SEP CX8 FPU ]
Symbol Capabilities:
index tag value
[2] CA_SUNW_ID hrt
[3] CA_SUNW_HW_1 0x40002 [ TSCP TSC ]
Symbols:
index value size type bind oth ver shndx name
[1] 0x000f306c 0x00000225 FUNC LOCL D 0 .text gettimeofday%hrt
[2] 0x000f2efc 0x00000165 FUNC LOCL D 0 .text gethrtime%hrt
Capabilities Chain Section: .SUNW_capchain
Capabilities family: gettimeofday
chainndx symndx name
1 [702] gettimeofday
2 [1] gettimeofday%hrt
Capabilities family: gethrtime
chainndx symndx name
4 [1939] gethrtime
5 [2] gethrtime%hrt
s11u1:jmcp $ elfdump -H /usr/lib/libc/libc_hwcap2.so.1
Capabilities Section: .SUNW_cap
Object Capabilities:
index tag value
[0] CA_SUNW_HW_1 0x1875 [ SSE2 SSE MMX CMOV AMD_SYSC CX8 FPU ]
Symbol Capabilities:
index tag value
[2] CA_SUNW_ID hrt
[3] CA_SUNW_HW_1 0x40002 [ TSCP TSC ]
Symbols:
index value size type bind oth ver shndx name
[1] 0x000f253c 0x00000225 FUNC LOCL D 0 .text gettimeofday%hrt
[2] 0x000f23cc 0x00000165 FUNC LOCL D 0 .text gethrtime%hrt
Capabilities Chain Section: .SUNW_capchain
Capabilities family: gettimeofday
chainndx symndx name
1 [702] gettimeofday
2 [1] gettimeofday%hrt
Capabilities family: gethrtime
chainndx symndx name
4 [1939] gethrtime
5 [2] gethrtime%hrt
Угадайте, что из перечисленного выше для систем AMD, а что для Intel?
У компоновщика Solaris есть умения загружать правильную библиотеку hwcap во время выполнения перед вызовом _init() вашего процесса.