Spec-Zone.ru › Eigen3

Объяснение проверки на выравнивание массивов

Привет! Вы видите эту веб-страницу, потому что ваша программа завершилась с ошибкой проверки, подобной этой:

my_program: path/to/eigen/Eigen/src/Core/DenseStorage.h:44:
Eigen::internal::matrix_array<T, Size, MatrixOptions, Align>::internal::matrix_array()
[with T = double, int Size = 2, int MatrixOptions = 2, bool Align = true]:
Assertion `(reinterpret_cast<size_t>(array) & (sizemask)) == 0 && "this assertion
is explained here: http://eigen.tuxfamily.org/dox-devel/group__TopicUnalignedArrayAssert.html
     READ THIS WEB PAGE !!! ****"' failed.

Существует 4 известных причины этой проблемы. Если вы можете нацелиться только на [c++17] с помощью недавнего компилятора (например, GCC>=7, clang>=5, MSVC>=19.12), то вам повезло: включение c++17 должно быть достаточно (если нет, пожалуйста, сообщите нам). В противном случае, пожалуйста, прочтите дальше, чтобы понять эти проблемы и узнать, как их исправить.

Где в моем коде находится причина проблемы?

Прежде всего, вам нужно выяснить, откуда в вашем коде была вызвана эта проверка. На первый взгляд, сообщение об ошибке не выглядит полезным, так как оно относится к файлу внутри Eigen! Однако, поскольку ваша программа завершилась аварийно, если вы можете воспроизвести сбой, вы можете получить трассировку стека с помощью любого отладчика. Например, если вы используете GCC, вы можете использовать отладчик GDB следующим образом:

$ gdb ./my_program          # Start GDB on your program
> run                       # Start running your program
...                         # Now reproduce the crash!
> bt                        # Obtain the backtrace

Теперь, когда вы точно знаете, где в вашем коде происходит проблема, прочтите дальше, чтобы понять, что вам нужно изменить.

Причина 1: Структуры, содержащие объекты Eigen в качестве членов

Если у вас есть код подобный этому,

class Foo
{
  //...
  Eigen::Vector4d v;
  //...
};
//...
Foo *foo = new Foo;

тогда вам нужно прочитать эту отдельную страницу: Структуры, содержащие члены Eigen.

Обратите внимание, что здесь Eigen::Vector4d используется только в качестве примера, более общим образом проблема возникает для всех типов Eigen с фиксированным размером и возможность векторизации.

Причина 2: Контейнеры STL или ручное выделение памяти

Если вы используете контейнеры STL, такие как std::vector, std::map, ..., с объектами Eigen или с классами, содержащими объекты Eigen, например так,

std::vector<Eigen::Matrix2d> my_vector;
struct my_class { ... Eigen::Matrix2d m; ... };
std::map<int, my_class> my_map;

тогда вам нужно прочитать эту отдельную страницу: Использование контейнеров STL с Eigen.

Обратите внимание, что здесь Eigen::Matrix2d используется только в качестве примера, более общим образом проблема возникает для всех типов Eigen с фиксированным размером и возможность векторизации и структур, содержащих такие объекты Eigen в качестве членов.

Такая же проблема будет наблюдаться у любых классов/функций, обходящих оператор new для выделения памяти, то есть выполняющих пользовательское выделение памяти, за которым следуют вызовы оператора placement new. Это, например, типичный случай с std::make_shared или std::allocate_shared, для которых решением является использование выровненного аллокатора, как подробно описано в решении для контейнеров STL.

Причина 3: Передача объектов Eigen по значению

Если какая-то функция в вашем коде получает объект Eigen, переданный по значению, например так,

void func(Eigen::Vector4d v);

тогда вам нужно прочитать эту отдельную страницу: Передача объектов Eigen по значению функциям.

Обратите внимание, что здесь Eigen::Vector4d используется только в качестве примера, более общим образом проблема возникает для всех типов Eigen с фиксированным размером и возможность векторизации.

Причина 4: Компилятор делает неправильное предположение об выравнивании стека (например, GCC на Windows)

Это обязательно для прочтения людям, использующим GCC на Windows (например, MinGW или TDM-GCC). Если у вас есть такая ошибка проверки в безобидной функции, объявляющей локальную переменную так:

void foo()
{
  Eigen::Quaternionf q;
  //...
}

тогда вам нужно прочитать эту отдельную страницу: Компилятор делает неправильное предположение о выравнивании стека.

Обратите внимание, что здесь Eigen::Quaternionf используется только в качестве примера, более общим образом проблема возникает для всех типов Eigen с фиксированным размером и возможность векторизации.

Общее объяснение этой проверки

Объекты Eigen с фиксированным размером и возможность векторизации должны быть созданы в правильно выровненных местах, в противном случае инструкции SIMD, обращаясь к ним, приведут к аварийному завершению. Например, цели SSE/NEON/MSA/Altivec/VSX потребуют выравнивания на 16 байт, а цели AVX и AVX512 могут потребовать выравнивания до 32 и 64 байт соответственно.

Eigen обычно заботится об этих проблемах выравнивания за вас, устанавливая атрибут выравнивания на них и перегружая их operator new.

Однако есть несколько частных случаев, когда эти настройки выравнивания перезаписываются: они являются возможными причинами этой проверки.

Мне не важна оптимальная векторизация, как избавиться от этой проблемы?

Три возможности:

  • Используйте опцию DontAlign для объектов Matrix, Array, Quaternion и т. д., которые вам доставляют проблемы. Таким образом, Eigen не будет пытаться перевыровнять их, и, следовательно, не будет предполагать какого-либо специального выравнивания. С другой стороны, вы будете платить за стоимость невыровненных загрузки/хранения для них, но на современных процессорах накладные расходы равны нулю или минимальны. См. здесь для примера.
  • Определите EIGEN_MAX_STATIC_ALIGN_BYTES в 0. Это отключит весь код статического выравнивания на 16 байт (и более), сохраняя выравнивание кучи на 16 байт (и более). Это имеет эффект векторизации объектов фиксированного размера (например, Matrix4d) с помощью невыровненных сохранений (как контролируется EIGEN_UNALIGNED_VECTORIZE), сохраняя при этом неизменной векторизацию объектов динамического размера (например, MatrixXd). В системах с 64 байтами вы также можете определить его как 16, чтобы отключить только 32 и 64 байта перевыравнивания. Но имейте в виду, что это нарушает совместимость ABI с поведением статического выравнивания по умолчанию.
  • Или определите как EIGEN_DONT_VECTORIZE, так и EIGEN_DISABLE_UNALIGNED_ARRAY_ASSERT. Это сохраняет код выравнивания на 16 байт (и более), сохраняя, таким образом, совместимость ABI, но полностью отключает векторизацию.

Если вы хотите узнать, почему определение EIGEN_DONT_VECTORIZE само по себе не отключает выравнивание на 16 байт (и более) и проверку, вот объяснение:

Это не отключает проверку, потому что в противном случае код, который работает нормально без векторизации, внезапно будет завершаться аварийно при включении векторизации. Это не отключает выравнивание на 16 байт (и более), потому что это означало бы, что векторизованный и не векторизованный код не являются взаимно совместимыми по ABI. Эта совместимость ABI очень важна, даже для людей, которые разрабатывают только внутреннее приложение, так как, например, может потребоваться иметь в одном приложении векторизованный путь и не векторизованный путь.

Как проверить, что мой код безопасен в отношении проблем с выравниванием?

К сожалению, в C++ нет возможности обнаружить какие-либо из упомянутых недостатков на этапе компиляции (хотя статические анализаторы становятся все более мощными и могут обнаруживать некоторые из них). Даже во время выполнения все, что мы можем сделать, это поймать некорректное невыровненное выделение и вызвать явную проверку, упомянутую в начале этой страницы. Поэтому, если ваша программа работает нормально на данной системе с некоторыми заданными флагами компиляции, это не гарантирует, что ваш код безопасен. Например, на большинстве 64-битных систем буферы выравниваются на границе 16 байт, и поэтому, если вы не включите набор инструкций AVX, ваш код будет работать нормально. С другой стороны, тот же самый код может завершиться проверкой, если перейти на более экзотическую платформу или включить инструкции AVX, которые по умолчанию требуют выравнивания на 32 байта.

Однако ситуация не безнадежна. Предполагая, что ваш код хорошо покрыт модульными тестами, вы можете проверить безопасность его выравнивания, связав его с пользовательской библиотекой malloc, возвращающей буферы, выровненные только на 8 байтов. Таким образом, все недостатки выравнивания должны появиться. Для этого вы также должны скомпилировать свою программу с EIGEN_MALLOC_ALREADY_ALIGNED=0.

© Eigen.
Licensed under the MPL2 License.
https://eigen.tuxfamily.org/dox/group__TopicUnalignedArrayAssert.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API