Обновление: alignof(Edge)
было 16 из-за long double
в x86-64 System V, так что это UB, чтобы иметь один по менее выровненному адресу. Это говорит G CC, что безопасно использовать movaps
.
IDK, почему при загрузке из (%rbp)
также не используется movaps
. Я думал, что подразумеваемый Edge не будет выровнен по 16 байтам, поэтому есть целый раздел этого ответа, основанный на этом предположении (которое я переместил в конец).
Для некоторых типов может потребоваться 16- выравнивание байтов, в частности long double
alignof(max_align_t) == 16
в системе x86-64 V. Для замены malloc
требуется возврат памяти, по крайней мере, такой, для выравнивания, для выделений 16 байтов или больше.
(Меньшие выделения, конечно, не могут содержать 16-байтовый объект и, следовательно, не требуют 16-байтового выравнивания. Вы можете запросить конкретный c экземпляр объекта для быть выровненным по alignas(16) int foo;
, но если сам тип имеет более высокое выравнивание, он также имеет большее sizeof
, поэтому массив все равно будет подчиняться нормальным правилам, а также чтобы каждый элемент удовлетворял требованию выравнивания.)
См. Также Почему при выравнивании доступа к памяти mmap иногда возникает ошибка на AMD64? , когда автоматическая векторизация со смещением uint16_t*
приводит к ошибке. Также Pascal Блог Cuoq о выравнивании и наличии объектов с меньшим выравниванием, чем alignof(T)
, является неопределенным поведением, и то, как допущение отсутствия UB глубоко подходит для компиляторов.
Выбор инструкций
G CC и clang используют movaps
всякий раз, когда они могут доказать, что память должна быть достаточно выровнена. (При условии отсутствия UB). На Core2 и более ранних версиях, а также K10 и более ранних версиях невыровненные хранилища работают медленно, даже если во время выполнения происходит выравнивание памяти.
Nehalem и Bulldozer изменили это, но G CC все еще использует movaps
даже при -mtune=haswell
или даже vmovaps
с -march=haswell
, хотя это может выполняться только на процессорах с дешевыми vmovups
.
MSV C, а я CC никогда не использую movaps
, что негативно сказывается на производительности на очень старых процессорах, но иногда позволяя избежать смещения данных. Они будут складывать выровненные нагрузки в операнды памяти для инструкций SSE, таких как paddd xmm0, [rdi]
(что требует выравнивания, в отличие от эквивалента AVX1), поэтому они все равно будут создавать код, который дает сбой при смещении иногда , но обычно только с включенной оптимизацией. ИМО это не особенно здорово.
alignof(Point)
должно быть только 8 (наследуя выравнивание своего наиболее выровненного члена, int64_t
). Таким образом, G CC может доказать только 8-байтовое выравнивание для произвольного Point
, а не 16.
Для хранения данных c, G CC может знать, что он решил выровнять массив на 16 и, следовательно, может использовать movaps
/ movdqa
для загрузки с него. (Кроме того, x86-64 System V ABI требует, чтобы массивы stati c размером 16 байт или более были выровнены на 16, поэтому G CC может принять это даже для глобала extern unsigned char buffer[]
, определенного в некотором другом модуле компиляции.)
Вы не показали определение для Edge
, поэтому IDK, почему он имеет 16-байтовое выравнивание, но, возможно, alignof(Edge) == 16
? В противном случае да, это может быть ошибка компилятора.
Но тот факт, что он загружает исходный объект Edge
из стека с movups
, может показаться, что alignof(Edge) < 16
Возможно raw_memory = __builtin_assume_aligned(raw_memory, 8);
может помочь? IDK, если это может сказать G CC принять более низкое выравнивание, чем он уже предполагал, исходя из других факторов.
Вы могли бы сказать G CC, что Edge
(или int
в этом отношении) всегда можно выровнять, определив typedef следующим образом:
typedef long __attribute__((aligned(1), may_alias)) unaligned_aliasing_long;
may_alias
на самом деле ортогонально выравниванию, но стоит упомянуть, потому что один из вариантов использования случаи для этого будут загружены из буфера char[]
для анализа потока байтов. В этом случае вы хотели бы оба. Это альтернатива использованию memcpy(tmp, src, sizeof(tmp));
для выполнения невыровненных безопасных строго псевдонимов нагрузок.
G CC использует may_alias
для определения __m128
и may_alias,aligned(1)
как часть определения _mm_loadu_ps
(intrinsi c для не выровненных SIMD-нагрузок, таких как movups
). (Вам не нужно may_alias
для загрузки вектора с плавающей точкой из массива float
, но вам нужно may_alias
для загрузки его из чего-то другого.) См. Также Is `reinterpret_cast`ing между аппаратным указателем вектора SIMD и соответствующим типом неопределенное поведение?
И посмотрите Почему strlen glibc должен быть настолько сложным для быстрой работы? для скалярного кода, который я считаю безопасен для заниженных / псевдонимов unsigned long
, в отличие от альтернативной реализации glib c C. (Который должен быть скомпилирован без -flto
, чтобы он не мог встраиваться в другие функции glib c и прерываться из-за нарушения строгого псевдонима.)
Распределители и предполагаемое выравнивание
(Этот раздел был написан, предполагая, что alignof(Edge) < 16
. Здесь дело не в этом, и атрибуты функции могут быть полезны для понимания, даже если они не являются причиной проблемы. И, вероятно, также не являются приемлемым обходным путем. )
Возможно, вы сможете использовать __attribute__ ((assume_aligned (8)))
на вашем распределителе, чтобы сообщить G CC о выравнивании возвращаемого указателя.
G CC, возможно, для некоторых допускается причина в том, что ваш распределитель возвращает память, используемую для любого объекта (и alignof(max_align_t) == 16
на x86-64 System V из-за long double
и других вещей, а также на Windows x64).
Если это не случай, вы можете сказать это. Это mmap
неправильное выравнивание Q & A , мы можем видеть, что G CC «знает» о malloc
и относится к нему специально. Но если ваша функция не имеет имени, определенного в ISO C или C ++, или атрибутов GNU C, это было бы удивительно. IDK, это лучшее предположение на основе того, что вы показали, если это не ошибка компилятора. (Это возможно.)
С руководство G CC :
void* my_alloc1 (size_t) __attribute__((assume_aligned (16)));
void* my_alloc2 (size_t) __attribute__((assume_aligned (32, 8)));
объявляет, что my_alloc1
возвращает 16-байтовые выровненные указатели и что my_alloc2
возвращает указатель, значение которого по модулю 32 равно 8.
Я не знаю, почему предполагается, что void*
, возвращенный функцией и приведенный к другому типу, будет иметь хоть и больше выравнивания, чем тип создаваемого объекта. Мы можем использовать movups
для загрузки Edge
откуда-то. Это может указывать на то, что alignof(Edge) < 16
.
Также имеет значение __attribute__((alloc_size(1)))
, чтобы сказать G CC, что первый аргумент функции - это размер. Если ваша функция принимает явное выравнивание в качестве аргумента, используйте alloc_align (position)
, чтобы указать это, в противном случае - нет.