Ответ 1
Прежде всего вам нужно уделить внимание двум типам распределения памяти:
-
Статическое распределение. Для правильного выравнивания автоматических переменных ваш тип требует правильной спецификации выравнивания (например,
__declspec(align(16))
,__attribute__((aligned(16)))
или вашего_MM_ALIGN16
). Но, к счастью, вам это нужно только в том случае, если требования к выравниванию, заданные членами типа (если они есть), недостаточны. Поэтому вам это не нужноSphere
, учитывая, что вашVector3
уже правильно выровнен. И если вашVector3
содержит член__m128
(что довольно вероятно, в противном случае я бы предложил сделать это), тогда вам это даже не понадобится дляVector3
. Таким образом, вам обычно не нужно связываться с атрибутами выравнивания, специфичными для компилятора. -
Динамическое распределение. Так много для легкой части. Проблема заключается в том, что С++ использует на самом низком уровне функцию распределения памяти с типом-агностиком для распределения любой динамической памяти. Это гарантирует только правильное выравнивание для всех стандартных типов, которое может быть 16 байт, но не гарантировано.
Чтобы это компенсировать, вам нужно перегрузить встроенный
operator new/delete
, чтобы реализовать собственное выделение памяти и использовать выровненную функцию распределения под капотом вместо старого старогоmalloc
. Перегрузкаoperator new/delete
- это тема сама по себе, но это не так сложно, как может показаться на первый взгляд (хотя вашего примера недостаточно), и вы можете прочитать об этом в этом отличном FAQ вопрос.К сожалению, вам нужно сделать это для каждого типа, у которого есть кто-то из членов, нуждающихся в нестандартном выравнивании, в вашем случае как
Sphere
, так иVector3
. Но вы можете сделать это немного проще, просто создайте пустой базовый класс с соответствующими перегрузками для этих операторов, а затем просто выведите все необходимые классы из этого базового класса.Большинство людей иногда забывают, что стандартный распределитель
std::alocator
использует глобальныйoperator new
для распределения всей памяти, поэтому ваши типы не будут работать со стандартными контейнерами (аstd::vector<Vector3>
не так редок прецедент). Что вам нужно сделать, это сделать свой собственный стандартный сопоставительный распределитель и использовать его. Но для удобства и безопасности на самом деле лучше просто специализироватьstd::allocator
для вашего типа (возможно, просто вывести его из вашего настраиваемого распределителя), чтобы он всегда использовался, и вам не нужно заботиться о том, чтобы использовать правильный распределитель каждый раз, когда вы используйтеstd::vector
. К сожалению, в этом случае вам нужно снова специализироваться на каждом выровненном типе, но с этим помогает небольшой злой макрос.Кроме того, вам нужно искать другие вещи, используя глобальный
operator new/delete
вместо вашего пользовательского, напримерstd::get_temporary_buffer
иstd::return_temporary_buffer
, и заботиться о них, если это необходимо.
К сожалению, пока нет гораздо лучшего подхода к этим проблемам, если вы не находитесь на платформе, которая изначально соответствует 16 и знает об этом. Или вы можете просто перегрузить глобальный operator new/delete
, чтобы всегда выровнять каждый блок памяти до 16 байтов и не заботиться о выравнивании каждого отдельного класса, содержащего член SSE, но я не знаю о последствиях этого подхода, В худшем случае это должно просто привести к потере памяти, но опять же вы обычно не выделяете небольшие объекты динамически в С++ (хотя std::list
и std::map
могут по-другому думать об этом).
Итак, подведем итог:
-
Соблюдайте правильное выравнивание статической памяти с помощью таких вещей, как
__declspec(align(16))
, но только если это не касается любого члена, что обычно происходит. -
Перегрузка
operator new/delete
для каждого типа, имеющего элемент с нестандартными требованиями к выравниванию. -
Сделать стандартным стандартным распределителем cunstom для использования в стандартных контейнерах выровненных типов или, еще лучше, специализировать
std::allocator
для каждого выровненного типа.
Наконец, некоторые общие советы. Часто вы получаете прибыль только SSE в вычислительно-тяжелых блоках при выполнении многих векторных операций. Чтобы упростить все проблемы с выравниванием, особенно проблемы ухода за каждым типом, содержащим Vector3
, было бы неплохо сделать специальный векторный тип SSE и использовать его только для длинных вычислений, используя нормальный вектор без SSE для хранения и переменные-члены.