Выравнивание памяти 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) в качестве базового адреса вашего массива. Это всегда будет выровнено.