#c #constructor #x86 #sse #avx
#c #конструктор #x86 #sse #avx
Вопрос:
Рассмотрим следующее:
// foo.h
class Foo
{
public:
int x = 2;
int y = 3;
void DoSomething_SSE();
void DoSomething_AVX();
// ( Implicit default constructor is generated "inline" here )
};
// Foo_AVX.cpp, compiled with -mavx or /arch:AVX
void Foo::DoSomething_AVX()
{
// AVX optimised implementation here
}
// Foo_SSE.cpp, compiled with -msse2 or /arch:SSE2
void Foo::DoSomething_SSE()
{
// SSE optimised implementation here
}
Вот проблема: компилятор сгенерирует подразумеваемый конструктор по умолчанию с ‘встроенной’ семантикой (примечание: встроенная семантика не означает, что функция обязательно будет встроенной) в каждой единице перевода, и — в случаях, когда конструктор не встроен — компоновщик затем выберет одну реализацию и отбросит другую.
Если компоновщик выберет конструктор, сгенерированный в модуле компиляции AVX, этот код завершит работу с недопустимой инструкцией на компьютере, который не поддерживает AVX.
Можно остановить сбой, введя явный конструктор по умолчанию, либо __forceinline (чтобы убедиться, что он встроен один раз в единицу компиляции), либо объявленный в заголовке и определенный в единице компиляции, которая компилируется с наименьшим набором команд общего знаменателя.
Однако, наверняка есть способ заставить язык справиться с этим лучше, чем писать фиктивные функции ..?
(llvm-clang 9.x.x / x64 в Mac OS X)
Комментарии:
1. Вы смотрели на gcc.gnu.org/onlinedocs/gcc-8.2.0/gcc /… или clang.llvm.org/docs/AttributeReference.html ? Хотя не знаю, работает ли это для конструкторов.
2. Почему вы связываете код, скомпилированный для разных архитектур, в один и тот же двоичный файл!?
3. Возможно, это окажется полезным: gcc.gnu.org/wiki/FunctionMultiVersioning
4. Дэн М.: Это сработало бы в простом случае, описанном выше, однако после этого невозможно помечать экземпляры шаблонов (например) атрибутами функции.
5. Майкл Кензел: Я прекрасно справляюсь с этим уже несколько лет — у них одна и та же базовая архитектура (x64), но разные наборы функций процессора, так что один исполняемый файл может выполняться оптимизированным на процессорах разных поколений — SSE2, SSE4, AVX, AVX2, AVX512. Только когда я начал больше полагаться на конструкторы по умолчанию, это стало проблемой.
Ответ №1:
Скомпилируйте единицы перевода AVX с помощью gcc или clang -mavx -fno-implement-inlines
; компоновщику придется найти символ из единиц перевода SSE, если функции просто не встроены.
Из руководства GCC:
-fno-implement-inlines
Для экономии места не создавайте встроенные копии функций, управляемых#pragma implementation
. Это приводит к ошибкам компоновщика, если эти функции не встроены везде, где они вызываются.
Clang также поддерживает эту опцию.
Это не отключает встраивание чего-либо, это только отключает выдачу автономного определения функций, объявленных как inline
или в определении класса.
При включенной оптимизации небольшой конструктор по умолчанию, подобный приведенному в вопросе, должен быть встроен (и использовать параметры целевого ISA текущей функции / модуля компиляции), что делает это неактуальным большую часть времени. Но это гарантирует, что неоптимизированные сборки будут работать должным образом на машинах, отличных от AVX.
Комментарии:
1. Большое вам спасибо, это поможет. Другое решение, которое я нашел после долгих поисков, описано ниже.
Ответ №2:
Похоже, что другой вариант — не использовать флаги компилятора для установки набора команд — оставить их по умолчанию и переносить только функции, для которых требуется расширенный набор команд:
#include Foo.h
// Switch on AVX optimisations for the function where they're needed
#pragma clang attribute push (__attribute__((target("arch=sandybridge"))), apply_to = function)
void Foo::DoSomething_AVX()
{
// AVX optimised implementation here
}
#pragma clang attribute pop
Использование атрибута #pragma clang push(…), хотя и немного более затянутое, чем простое [[]]
или __attribute__(())
, по-видимому, имеет то преимущество, что атрибут автоматически применяется к любому шаблонному коду и т.д., созданному из области действия pragma.
Комментарии:
1. Все, что встроено в функцию, помеченную
__attribute__((target(arch=sandybridge)))
, будет использовать тот же кодовый генератор. Создание экземпляров шаблонов, которые не встроены в AVX code-gen, может быть опасным, если код, отличный от AVX, также может использовать эти автономные функции. Интересная идея, однако.
Ответ №3:
Поместите реализацию в отдельный cpp-файл и вуаля, все работает. Другой способ — сделать эти функции / методы / конструкторы встроенными. Третий способ (зависит от компилятора) — установить для них атрибут ‘weak reference’.
Комментарии:
1. Упущенный момент: нет реализации. Это функции по умолчанию.