Выравнивание памяти G ++ SSE в стеке

Я пытаюсь переписать трассировщик лучей, используя Streaming SIMD Extensions. Мой исходный трассировщик лучей использовал встроенную сборку и инструкции movups для загрузки данных в регистры xmm. Я читал, что встроенные функции компилятора ненамного медленнее встроенной сборки (подозреваю, что могу даже увеличить скорость, избегая невыровненных обращений к памяти) и намного более переносимы, поэтому я пытаюсь перенести свой код SSE на использование встроенных функций в xmmintrin.h . Основным затронутым классом является vector, который выглядит примерно так:

#include "xmmintrin.h"
union vector {
    __m128 simd;
    float raw[4];
    //some constructors
    //a bunch of functions and operators
} __attribute__ ((aligned (16)));

Ранее я читал, что компилятор g ++ автоматически выделяет структуры по границам памяти, равным размеру самой большой переменной-члена, но, похоже, этого не происходит, и атрибут выравнивания не помогает. Мои исследования показывают, что это вероятно, потому что я выделяю в стеке целую кучу локальных векторов функций, и что выравнивание в стеке не гарантируется в x86. Есть ли способ заставить это согласование? Я должен упомянуть, что это работает под родным Linux x86 на 32-битной машине, а не на Cygwin. Я намерен реализовать многопоточность в этом приложении в дальнейшем, поэтому объявление вызывающих ошибку экземпляров вектора статическими не является вариантом. При необходимости я готов увеличить размер своей векторной структуры данных.


person Octavianus    schedule 11.02.2011    source источник
comment
Если у вас есть последняя версия g ++, которая поддерживает std::aligned_storage, вы можете получить выровненное хранилище переносимым способом, который также будет работать с другими компиляторами.   -  person James McNellis    schedule 11.02.2011
comment
что, если вы обойдете объединение и просто воспользуетесь прямым __m128? это что-нибудь меняет?   -  person Anycorn    schedule 11.02.2011
comment
Полный обход союза значительно затрудняет доступ к отдельным членам. У меня также есть класс vectorpacket, который получает больше преимуществ от SSE, чем стандартные векторы, но в качестве примера мой тест показывает, что для меня быстрее добавлять элементы одного точечного продукта последовательно вместо того, чтобы повторно перетасовывать регистр в добавить, оставаясь в моем блоке SSE. Я не знаком с std::aligned_storage; Тем не менее, моя машина имеет g ++ 4.4.3, но есть вторая машина, на которой я надеялся запустить ее, которая заблокирована на 3.4.6.   -  person Octavianus    schedule 11.02.2011
comment
software.intel.com/en-us/forums/showthread. php? t = 63876 обратите внимание, что они приводят массив к типам m128. вы можете аналогичным образом преобразовать m128 в массив.   -  person Anycorn    schedule 11.02.2011
comment
@Anycorn: нет, (float*)my_m128 небезопасно. Свойство «may-alias-something» векторных типов Intel действует только в одном направлении, а именно как вы можете получить доступ к чему-либо с помощью char*, но не гарантируется безопасность доступа к char[] с помощью int*. (Это безопасно в MSVC, который похож на gcc -fno-strict-aliasing, но в других компиляторах вы должны использовать объединения или перетасовать встроенные функции для доступа к элементам векторов.) См. печать переменной __m128i для примера.   -  person Peter Cordes    schedule 02.05.2019


Ответы (5)


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

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

person Matthieu M.    schedule 11.02.2011
comment
Это выглядит привлекательно, но где мне найти std :: align_storage? Google совершенно бесполезен; он предлагает type_traits, но включение этого не позволяет мне скомпилировать с ним. Неужели выровненное хранилище настолько ново, что g ++ 4.4.3 его не поддерживает? - person Octavianus; 11.02.2011
comment
@Octavianus: возможно, у меня под рукой g ++ 4.4.3 нет. Возможно, вам стоит попробовать использовать std::tr1::aligned_storage вместо (в <type_traits>) - person Matthieu M.; 12.02.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.

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

person Sparky    schedule 11.02.2011
comment
__M128 typedef идентичен вашему typedef для v4f_t, так что я боюсь, что это то, что у меня есть. - person Octavianus; 12.02.2011
comment
@Octavianus: Фактический __m128 typedef также включает атрибут may_alias, что делает безопасным создание __m128* указателей на любой произвольный объект, даже не float данные. (т.е. похоже, что char* может быть псевдонимом на что угодно). Но да, __attribute__((aligned(16))) в профсоюзе излишне; он уже получил это от участника v4f_t или __m128. Кроме того, собственный векторный синтаксис GNU C позволяет индексировать в __m128 v, например, v[3], поэтому объединение также является избыточным. (За исключением доступа к нему с помощью float*; строгий псевдоним не работает в обоих направлениях. Или для совместимости с MSVC.) - person Peter Cordes; 02.05.2019

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

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

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

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

person Paul R    schedule 11.02.2011

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

Однако в других ответах есть хорошие обходные пути.

person Frederik Slijkerman    schedule 11.02.2011
comment
Это не ошибка компилятора. Многие машины по умолчанию используют 16-байтовое выравнивание в стеке, но некоторые старые машины не гарантируют ничего, превышающего 8-байтовое. - person Octavianus; 11.02.2011
comment
__m128 гарантирует 16-байтовое выравнивание - person Frederik Slijkerman; 13.02.2011
comment
Да. Но если вы вставите его в структуру, он, по-видимому, гарантирует только 16-байтовое выравнивание относительно начала структуры. - person Octavianus; 14.02.2011
comment
@Octavianus: Тогда это ошибка компилятора. alignof(union) должен быть ›= alignof() его наиболее выровненным участником. - person Peter Cordes; 02.05.2019

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

person Ben Voigt    schedule 11.02.2011
comment
Моя основная проблема - это объявление локальных экземпляров этих объектов - на самом деле мне не нужен их массив. - person Octavianus; 11.02.2011
comment
@Octavianus: Это, безусловно, можно использовать для N = 1, просто у вас больше накладных расходов. - person Ben Voigt; 11.02.2011