Выравнивание памяти SSE g++ в стеке
Я пытаюсь перезаписать raytracer с помощью Streaming SIMD Extensions. Мой оригинальный raytracer использовал встроенную сборку и movups инструкции для загрузки данных в регистры xmm. Я прочитал, что внутренняя среда компилятора не значительно медленнее, чем встроенная сборка (я подозреваю, что могу даже увеличить скорость, избегая неприглашенных запросов к памяти) и гораздо более портативный, поэтому я пытаюсь перенести свой код SSE, чтобы использовать встроенные функции в xmmintrin.h, Первичным классом является вектор, который выглядит примерно так:
#include "xmmintrin.h"
union vector {
__m128 simd;
float raw[4];
//some constructors
//a bunch of functions and operators
} __attribute__ ((aligned (16)));
Я уже читал, что компилятор g++ автоматически выделяет структуры по границам памяти, равным размерам наибольшей переменной-члена, но это, похоже, не происходит, и выравниваемый атрибут не помогает. Мои исследования показывают, что это, вероятно, связано с тем, что я выделяю целую кучу функциональных локальных векторов в стеке, а выравнивание в стеке не гарантируется в x86. Есть ли способ заставить это выравнивание? Я должен упомянуть, что это работает под собственной x86 Linux на 32-битной машине, а не Cygwin. Я намерен реализовать многопоточность в этом приложении дальше по строке, поэтому объявление экземпляров оскорбительных векторов как статических не является вариантом. Я желаю увеличить размер моей структуры векторных данных, если это необходимо.
Ответы
Ответ 1
Самый простой способ - std::aligned_storage
, который выполняет выравнивание как второй параметр.
Если у вас его еще нет, вы можете проверить версию Boost.
Затем вы можете создать свой союз:
union vector {
__m128 simd;
std::aligned_storage<16, 16> alignment_only;
}
Наконец, если это не сработает, вы всегда можете создать свой собственный маленький класс:
template <typename Type, intptr_t Align> // Align must be a power of 2
class RawStorage
{
public:
Type* operator->() {
return reinterpret_cast<Type const*>(aligned());
}
Type const* operator->() const {
return reinterpret_cast<Type const*>(aligned());
}
Type& operator*() { return *(operator->()); }
Type const& operator*() const { return *(operator->()); }
private:
unsigned char* aligned() {
if (data & ~(Align-1) == data) { return data; }
return (data + Align) & ~(Align-1);
}
unsigned char data[sizeof(Type) + Align - 1];
};
Он будет выделять немного больше памяти, чем необходимо, но этот способ выравнивания гарантирован.
int main(int argc, char* argv[])
{
RawStorage<__m128, 16> simd;
*simd = /* ... */;
return 0;
}
Если повезет, компилятор сможет оптимизировать материал выравнивания указателя, если он обнаружит правильность выравнивания.
Ответ 2
Несколько недель назад я переписал старую трассировку лучей из моих университетских дней, обновив ее, чтобы запустить ее на 64-битном Linux и использовать инструкции SIMD. (Старая версия, кстати, работала под DOS на 486, чтобы дать вам представление о том, когда я последний что-нибудь с ней сделал).
Там могут быть лучшие способы сделать это, но вот что я сделал...
typedef float v4f_t __attribute__((vector_size (16)));
class Vector {
...
union {
v4f_t simd;
float f[4];
} __attribute__ ((aligned (16)));
...
};
Разборка моего скомпилированного двоичного файла показала, что он действительно использовал инструкцию movaps.
Надеюсь, что это поможет.
Ответ 3
Я использую этот союзный трюк все время с __m128
, и он работает с GCC на Mac и Visual С++ в Windows, поэтому это должно быть ошибкой в используемом вами компиляторе.
Другие ответы содержат хорошие обходные пути.
Ответ 4
Обычно вам нужно всего лишь:
union vector {
__m128 simd;
float raw[4];
};
то есть. нет дополнительного __attribute__ ((aligned (16)))
, необходимого для самого объединения.
Это работает, как и ожидалось, в почти каждом компиляторе, который я когда-либо использовал, с заметным исключением gcc 2.95.2 в тот же день, который в некоторых случаях использовал для выравнивания стека.
Ответ 5
Если вам нужен массив из N этих объектов, выделите vector raw[N+1]
и используйте vector* const array = reinterpret_cast<vector*>(reinterpret_cast<intptr_t>(raw+1) & ~15)
в качестве базового адреса вашего массива. Это всегда будет выровнено.