SSE, внутренности и выравнивание

Я написал трехмерный векторный class, используя множество встроенных компиляторов SSE. Все работало нормально, пока я не начал устанавливать classы с 3D-вектором в качестве члена с новым. Я пережил странные сбои в режиме выпуска, но не в режиме отладки, а наоборот.

Поэтому я прочитал несколько статей и решил, что мне нужно выровнять classы, владеющие экземпляром трехмерного векторного classа, и 16 байт. Поэтому я просто добавил _MM_ALIGN16 ( __declspec(align(16) ) перед classами следующим образом:

 _MM_ALIGN16 struct Sphere { // .... Vector3 point; float radius }; 

Казалось, что это проблема сначала. Но после изменения кода моя программа снова начала сбой чересчур. Я снова искал в Интернете и нашел статью в блоге . Я попробовал то, что сделал автор, Эрнст Хот, для решения проблемы, и это работает и для меня. Я добавил новые и удаляю операторов в свои classы следующим образом:

 _MM_ALIGN16 struct Sphere { // .... void *operator new (unsigned int size) { return _mm_malloc(size, 16); } void operator delete (void *p) { _mm_free(p); } Vector3 point; float radius }; 

Эрнст упоминает, что этот подход может быть проблематичным, но он просто ссылается на форум, который больше не существует, не объясняя, почему это может быть проблематичным.

Поэтому мои вопросы:

  1. В чем проблема с определением операторов?

  2. Почему добавление _MM_ALIGN16 в определение classа достаточно?

  3. Каков наилучший способ решения проблем выравнивания, связанных с встроенными функциями SSE?

Прежде всего, вы должны заботиться о двух типах распределения памяти:

  • Статическое распределение. Для правильной выверки автоматических переменных ваш тип требует правильной спецификации выравнивания (например, __declspec(align(16)) , __attribute__((aligned(16))) или ваш _MM_ALIGN16 ). Но, к счастью, вам это нужно только в том случае, если требования к выравниванию, заданные членами типа (если они есть), недостаточны. Поэтому вам не нужно это для вас в Sphere , учитывая, что ваш Vector3 уже правильно выровнен. И если ваш Vector3 содержит член __m128 (что довольно вероятно, в противном случае я бы предложил сделать это), тогда вам даже не понадобится его для Vector3 . Поэтому вам обычно не нужно возиться с атрибутами выравнивания, специфичными для компилятора.

  • Динамическое распределение. Так много для легкой части. Проблема заключается в том, что C ++ использует на самом низком уровне функцию распределения памяти с типом-агностиком для распределения любой динамической памяти. Это гарантирует только правильное выравнивание для всех стандартных типов, которое может быть 16 байт, но не гарантировано.

    Чтобы это компенсировалось, вам нужно перегрузить встроенный operator new/delete чтобы реализовать собственное выделение памяти и использовать выровненную функцию распределения под капотом вместо старого старого malloc . Перегрузка operator new/delete – это тема сама по себе, но это не так сложно, как может показаться на первый взгляд (хотя вашего примера недостаточно), и вы можете прочитать об этом в этом замечательном вопросе FAQ .

    К сожалению, вы должны сделать это для каждого типа, в котором любой член нуждается в нестандартном выравнивании, в вашем случае как Sphere и Vector3 . Но то, что вы можете сделать, чтобы сделать это немного проще, – это просто создать пустой базовый class с соответствующими перегрузками для этих операторов, а затем просто извлечь все необходимые classы из этого базового classа.

    То, что большинство людей иногда забывает, состоит в том, что стандартный распределитель std::alocator использует глобальный operator new для распределения всей памяти, поэтому ваши типы не будут работать со стандартными контейнерами (а std::vector редкий случай использования). Что вам нужно сделать, это сделать свой собственный стандартный сопоставительный распределитель и использовать его. Но для удобства и безопасности на самом деле лучше просто специализировать std::allocator для вашего типа (возможно, просто вывести его из вашего настраиваемого распределителя), чтобы он всегда использовался, и вам не нужно заботиться о том, чтобы каждый раз использовать правильный распределитель вы используете std::vector . К сожалению, в этом случае вам нужно снова специализироваться на каждом выровненном типе, но с этим помогает небольшой злой макрос.

    Кроме того, вам нужно искать другие вещи, используя глобальный operator new/delete вместо вашего пользовательского, например std::get_temporary_buffer и std::return_temporary_buffer , и заботиться о них, если это необходимо.

К сожалению, пока еще нет гораздо лучшего подхода к этим проблемам, если вы не находитесь на платформе, которая изначально соответствует 16 и знает об этом . Или вы можете просто перегрузить глобальный operator new/delete чтобы всегда выровнять каждый блок памяти до 16 байтов и не заботиться о выравнивании каждого classа, содержащего член SSE, но я не знаю о последствиях этого подход. В худшем случае это должно просто привести к растрате памяти, но опять же вы обычно не выделяете небольшие объекты динамически в C ++ (хотя std::list и std::map могут по-другому думать об этом).

Итак, подведем итог:

  • __declspec(align(16)) правильное выравнивание статической памяти с помощью таких вещей, как __declspec(align(16)) , но только если это не касается любого члена, что обычно происходит.

  • operator new/delete перегрузки operator new/delete для каждого типа, имеющего элемент с нестандартными требованиями к выравниванию.

  • Создайте стандартно-совместимый распределитель cunstom для использования в стандартных контейнерах выровненных типов или, еще лучше, специализируйте std::allocator для каждого выровненного типа.


Наконец, некоторые общие советы. Часто вы получаете прибыль только SSE в вычислительно-тяжелых блоках при выполнении многих векторных операций. Чтобы упростить все проблемы с выравниванием, особенно проблемы, связанные с ориентацией каждого типа, содержащего Vector3 , было бы неплохо придумать специальный векторный тип SSE и использовать его только для длинных вычислений, используя обычный, -SSE для хранения и переменных-членов.

В принципе, вам нужно убедиться, что ваши векторы правильно выровнены, потому что типы векторов SIMD обычно имеют более высокие требования к выравниванию, чем любые встроенные типы.

Это требует выполнения следующих действий:

  1. Убедитесь, что Vector3 правильно выровнен, когда он находится в стеке или член структуры. Это делается путем применения __attribute__((aligned(32))) к classу Vector3 (или любой другой атрибут поддерживается вашим компилятором). Обратите внимание, что вам не нужно применять атрибут к структурам, содержащим Vector3 , что необязательно и недостаточно (т. Vector3 Не нужно применять его к Sphere ).

  2. Убедитесь, что Vector3 или его окружающая структура правильно выровнены при использовании распределения кучи. Это делается с помощью posix_memalign() (или аналогичной функции для вашей платформы) вместо использования простого malloc() или operator new() поскольку последние два выравнивают память для встроенных типов (обычно 8 или 16 байт), что не гарантированно будет достаточно для SIMD-типов.

  1. Проблема с операторами заключается в том, что сами по себе они недостаточны. Они не влияют на распределения стека, для которых вам все еще нужен __declspec(align(16)) .

  2. __declspec(align(16)) влияет на то, как компилятор помещает объекты в память, если и только если у него есть выбор. Для новых объектов компилятор не имеет выбора, кроме как использовать память, возвращаемую operator new .

  3. В идеале используйте компилятор, который обрабатывает их изначально. Нет никакой теоретической причины, почему к ним нужно относиться иначе, чем к double . Еще, прочитайте документацию компилятора для обходных решений. Каждый компилятор с ограниченными возможностями будет иметь свой собственный набор проблем и, следовательно, собственный набор обходных решений.

  • SIMD, подписанный с неподписанным умножением для 64-разрядных * 64-бит до 128 бит
  • Как использовать инструкции Fused Multiply-Add (FMA) с SSE / AVX
  • Каковы наилучшие последовательности команд для генерации векторных констант «на лету»?
  • SSE целочисленное деление?
  • Использование инструкций процессора AVX: низкая производительность без «/ arch: AVX»
  • Как решить проблему 32-байтового выравнивания для операций загрузки / хранения AVX?
  • Векторизация с неуравновешенными буферами: использование VMASKMOVPS: создание маски из подсчета несоосности? Или не использовать этот insn вообще
  • Почему этот код SSE в 6 раз медленнее без VZEROUPPER на Skylake?
  • AVX2, что является наиболее эффективным способом для упаковки влево на основе маски?
  • Как слить скаляр в вектор без компилятора, теряющего инструкцию обнуления верхних элементов? Ограничение дизайна в встроенных средах Intel?
  • Как проверить, поддерживает ли процессор набор инструкций SSE3?
  • Interesting Posts

    почему sizeof (‘a’) равен 4 в C?

    Что произойдет, если я извлечу свой жесткий диск из текущего компьютера и поставлю его на новый компьютер?

    Защищенный паролем iTextSharp PDF

    Как разрабатывать плагины для собственного браузера для Android

    Являются ли командная строка и MS-DOS одинаковыми?

    Как обрабатывать несколько таймеров обратного отсчета в ListView?

    GMail появляется, чтобы игнорировать ответ

    Автоматическое построение версий Golang

    SSH: сбой с закрытым ключом, работает с копией ключа, расположенного в другом месте

    Скопируйте все установленные программы и файлы на жесткий диск (который имеет 32-битную Windows 7) и клонируйте / перенесите его на другой компьютер с 64-разрядной версией Windows 7

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

    Не удается установить Windows SDK 7.1 на Windows 10

    Поиск ключевого слова конфликт в firefox

    Как указать различные макеты в файле ViewStart бритвы ASP.NET MVC 3?

    Случайная перетасовка массива

    Давайте будем гением компьютера.