Использование std:: atomic с выровненными классами

У меня есть класс mat4, матрица 4x4, которая использует sse intrinsics. Этот класс выровнен с использованием _MM_ALIGN16, поскольку он сохраняет матрицу как набор __m128. Проблема в том, что когда я объявляю atomic<mat4>, мой компилятор кричит на меня:

f:\program files (x86)\microsoft visual studio 12.0\vc\include\atomic(504): error C2719: '_Val': formal parameter with __declspec(align('16')) won't be aligned

Это та самая ошибка, которую я получаю, когда пытаюсь передать любой класс, выровненный с _MM_ALIGN16, в качестве аргумента для функции (без использования const &).

Как я могу объявить атомную версию моего класса mat4?

Ответ 1

Компилятор MSC никогда не поддерживал более 4 байтов выравнивания для параметров в стеке x86, и нет обходного пути.

Вы можете проверить это самостоятельно, компилируя

struct A { __declspec(align(4)) int x; }; 
void foo(A a) {}                      

против

// won't compile, alignment guarantee can't be fulfilled
struct A { __declspec(align(8)) int x; };

против

// __m128d is naturally aligned, again - won't compile
struct A { __m128d x; };

Как правило, MSC освобождается следующим:

Вы не можете указать выравнивание параметров функции.

align (С++)

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

Компилятор x86 использует другой метод для выравнивания стека. От default, стек выравнивается по 4 байт. Хотя это пространство вы можете видеть, что есть некоторые типы данных, которые должны быть 8-байт выровнен, и что, чтобы получить хорошую производительность, 16-байтовый Иногда требуется выравнивание. Компилятор может определить, на некоторых случаев динамическое 8-байтовое выравнивание стека было бы полезно, когда в стеке есть двойные значения.

Компилятор делает это двумя способами. Во-первых, компилятор может использовать генерация кода времени (LTCG), когда пользователь задает компилировать и связывать время, чтобы генерировать дерево вызовов для полного программа. При этом он может определять области дерева вызовов, где 8-байтовое выравнивание стека было бы полезно, и оно определяет вызывающие сайты, где динамическое выравнивание стека получает лучший выигрыш. второй способ используется, когда функция имеет двойные значения в стеке, но, по какой-то причине, еще не выровнен по 8 байт. Компилятор применяет эвристику (которая улучшается с каждой итерацией компилятор), чтобы определить, должна ли функция динамически 8-байт выровнен.

Выравнивание данных Windows на IPF, x86 и x64

Таким образом, пока вы используете MSC с 32-битным набором инструментов платформы, эта проблема неизбежна.

x64 ABI был явно о выравнивании, определяя, что нетривиальные структуры или структуры над определенными размерами передаются как параметр указателя. Это описано в разделе в разделе 3.2.3 ABI, и MSC должен был реализовать это, чтобы быть совместимым с ABI.

Путь 1: Используйте другую инструментальную цепочку компилятора Windows: GCC или ICC.

Путь 2: переход к 64-разрядной платформе MSC-инструментария

Путь 3: Сократите свои варианты использования до std::atomic<T> с помощью T=__m128d, так как будет возможно пропустить стек и передать переменную в регистр XMM напрямую.

Ответ 2

atomic<T>, вероятно, имеет конструктор, которому передается копия T как (формальный) параметр. Например, в заголовке atomic, упакованном вместе с GCC 4.5:

97: atomic(_Tp __i) : _M_i(__i) { }

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

Даже если компилятор допустил это, этот подход понесет значительный штраф за производительность. Предполагая, что вы пытаетесь оптимизировать скорость, я бы использовал менее мелкозернистый подход к доступу к памяти. Либо заблокируйте доступ к блоку памяти, выполняя серию вычислений, либо явно создавая вашу программу, чтобы потоки никогда не пытались получить доступ к одному и тому же фрагменту памяти.

Ответ 3

Я столкнулся с аналогичной проблемой, используя Agner Fog vectorclass в MSVC. Проблема возникает в 32-битном режиме. Если вы скомпилируете в режиме 64-битного режима, я не думаю, что у вас будет эта проблема. В Windows и Unix все переменные в стеке выравниваются до 16 байтов в 64-битном режиме, но не обязательно в 32-битном режиме. В своем руководстве по ошибкам времени компиляции он пишет

"C2719: формальный параметр с __declspec (align ('16 ')) не будет выровнен". Компилятор Microsoft не может обрабатывать векторы в качестве параметров функции. Самое простое решение - изменить параметр на константу, например: Vec4f my_function (Vec4f const и x) {...}

Итак, если вы используете ссылку на const (как вы упомянули), когда вы передаете свой класс функции, она также должна работать в 32-битном режиме.

Изменить: Исходя из этого Автономная, совместимая с STL реализация std::vector Я думаю, вы можете использовать "тонкую упаковку". Что-то вроде.

template <typename T>
struct wrapper : public T
{
    wrapper() {}
    wrapper(const T& rhs) : T(rhs) {}
};

struct __declspec(align(64)) mat4
{
    //float x, y, z, w;
};

int main()
{
    atomic< wrapper<mat4> > m;  // OK, no C2719 error
    return 0;
}

Ответ 4

Я не утверждаю, что понимаю, как __declspec(align(foo)) должен работать, но эта стандартная программа на С++ компилируется и работает отлично в gcc и clang с помощью alignas(16):

struct alignas(16) mat4 {
    float some_floats[4][4];
};

std::atomic<mat4> am4;
static_assert(alignof(decltype(am4)) == 16,
              "Jabberwocky is killing user.");

int main() {
    static const mat4 foo = {{
        { 1, 2, 3, 4 },
        { 1, 2, 3, 4 },
        { 1, 2, 3, 4 },
        { 1, 2, 3, 4 }
    }};
    am4 = foo;
}