G ++ SSE выравнивание памяти в стеке - PullRequest
11 голосов
/ 11 февраля 2011

Я пытаюсь переписать raytracer с помощью потоковых SIMD-расширений.Мой оригинальный 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.Я намереваюсь реализовать многопоточность в этом приложении дальше, поэтому объявлять статичные экземпляры-нарушители статичными не вариант.Я готов увеличить размер моей векторной структуры данных, если это необходимо.

Ответы [ 5 ]

6 голосов
/ 11 февраля 2011

Самый простой способ - 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;
}

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

3 голосов
/ 11 февраля 2011

Несколько недель назад я переписал старое задание по трассировке лучей из моих университетских дней, обновив его, чтобы оно работало на 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 .

Надеюсь, это поможет.

2 голосов
/ 11 февраля 2011

Обычно все, что вам нужно, это:

union vector {
    __m128 simd;
    float raw[4];
};

т.е. для самого объединения не требуется никаких дополнительных __attribute__ ((aligned (16))).

Это работает, как и ожидалось, практически на каждом компиляторе, который у меня когда-либо был.используется, за исключением заметного gcc 2.95.2, который в некоторых случаях испортил выравнивание стека.

1 голос
/ 11 февраля 2011

Я все время использую этот трюк объединения с __m128, и он работает с GCC на Mac и Visual C ++ в Windows, так что это должно быть ошибкой в ​​используемом вами компиляторе.

Другие ответысодержат хорошие обходные пути, хотя.

0 голосов
/ 11 февраля 2011

Если вам нужен массив из N этих объектов, выделите vector raw[N+1] и используйте vector* const array = reinterpret_cast<vector*>(reinterpret_cast<intptr_t>(raw+1) & ~15) в качестве базового адреса вашего массива.Это всегда будет выравниваться.

...