Это какая-то проблема с выравниванием?
Почти наверняка.
Компиляторы
C предполагают, что объект __m128
имеет 16-байтовое выравнивание, и используют movaps
, чтобы загрузить / сохранить его, или использовать его как операнд памяти для других инструкций SSE (например, * 1011). *). Эти применения будут давать сбой во время выполнения, если указатель не имеет 16-байтовое выравнивание.
Но вы не сказали Python выделить float Elements[4][4]
с какой-либо гарантией выравнивания, поэтому передача указателей на C даст вам недействительные union
объекты, которые нарушают требование выравнивания объединения достаточно для наиболее выровненного члена.
Если вы не можете заставить Python гарантировать 16-байтовое выравнивание ваших объектов , то вам придется изменить C, чтобы он все еще работал (немного менее эффективно). Компиляция с включенным AVX (gcc -O3 -march=native
на процессорах AVX) позволит компилятору использовать невыровненные 16-байтовые векторы в качестве операндов памяти. Но он по-прежнему не сделает смещенным __m128
сейфом, потому что он все равно будет храниться с vmovaps
, а не vmovups
.
Современное оборудование имеет эффективную поддержку выравнивания нагрузки, но разбиение строк кэша все еще не идеально. Число команд также хуже, потому что в AVX компилятору придется использовать отдельные movups
загрузки вместо addps xmm0, [mem]
для данных, которые нужно загружать только один раз.
В C удалите элемент __m128
и используйте _mm_loadu_ps()
для выравнивания нагрузки.
typedef struct my_mat4 { float Elements[4][4]; } my_mat4;
static inline
__m128 load_vec(const struct my_mat4 *m4, size_t idx) {
_mm_loadu_ps(&m4->Elements[idx][0]);
}
С GNU C: переопределите ваш союз с версией без выравнивания __m128
Было бы наиболее эффективно заставить Python выравнивать ваши объекты, но если нет, то это позволит вам скомпилировать существующий код с одним изменением вашего объекта:
__m128
определяется в терминах собственных векторов GNU C в xmmintrin.h # 69 . (Другие компиляторы, которые поддерживают расширения GNU, совместимы, по крайней мере, Clang.)
typedef float __m128 attribute ((vector_size (16), may_alias));
Заголовок уже определяет невыровненный __m128_u
, который также использует aligned(1)
. Мы можем использовать aligned(4)
, чтобы гарантировать, что он как минимум выровнен по границе float
, если это поможет.
Это просто работает, потому что разные версии выравнивания одного и того же векторного типа свободно конвертируемы, поэтому код, передающий его встроенным функциям, компилируется без предупреждений (даже при -Wall
).
typedef float __attribute((vector_size(16), aligned(4))) unaligned__m128;
// I left out may_alias, only matters if you're using unaligned__m128* to load from non-float data.
// Probably doesn't hurt code-gen if you aren't using unaligned__m128* at all, just objects
//#define __m128 unaligned__m128 // not needed
typedef union my_mat4 {
float Elements[4][4];
unaligned__m128 Rows[4];
} my_mat4;
Функции, использующие этот тип, прекрасно компилируются ( gcc8.1 в проводнике компилятора Godbolt ). (Вы могли бы также написать m4->Rows[1] + m4->Rows[2]
, даже в C, а не в C ++, потому что собственные векторы GNU C отображают операторы C на операции для каждого элемента.
__m128 use_row(union my_mat4 *m4) {
__m128 tmp = _mm_add_ps(m4->Rows[1], m4->Rows[2]);
m4->Rows[3] = tmp;
return tmp;
}
Всего с -O3
(без марша) мы получаем
movups xmm0, XMMWORD PTR [rdi+32] # unaligned loads
movups xmm1, XMMWORD PTR [rdi+16]
addps xmm0, xmm1
movups XMMWORD PTR [rdi+48], xmm0 # unaligned store
ret
Но с -mavx
(например, с помощью -march=haswell
) мы получаем
use_row(my_mat4*):
vmovups xmm1, XMMWORD PTR [rdi+32]
vaddps xmm0, xmm1, XMMWORD PTR [rdi+16] # unaligned memory source is ok for AVX
vmovups XMMWORD PTR [rdi+48], xmm0
ret
Конечно, вы хотите, чтобы эти функции были встроенными, я только сделал их не встроенными, чтобы я мог посмотреть, как они компилируются. ( Как убрать "шум" из вывода сборки GCC / clang? ).
Кстати, определение MAT4_MATH__USE_SSE
может изменить ABI, если вы когда-либо используете этот союз как член более широкой структуры. struct {int foo; my_mat4 m4; };
требуется 12 байтов заполнения, если my_mat4
выровнено, или нет заполнения в противном случае.
Если вы скомпилируете немного C с и немного C без макроса def, вы можете сделать что-то вроде этого (, если вы решили проблему заставить Python выравнивать объекты ):
#include <stdalign.h>
// give the same alignment regardless of whether the macro is defined.
typedef union my_mat4
{
alignas(16) float Elements[4][4];
#ifdef MAT4_MATH__USE_SSE
__m128 Rows[4];
#endif
} my_mat4;
Или нет, если вы не хотите гарантировать выравнивание, когда макрос не определен.