C : Как запретить конструктору по умолчанию использовать AVX для инициализации

#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. Упущенный момент: нет реализации. Это функции по умолчанию.