Ответ 1
Стандартные распределители обычно выровнены только с alignof(max_align_t)
, который часто составляет 16B, например long double
в x86-64 System V ABI. Но в некоторых 32-разрядных ABI это только 8B, поэтому его даже недостаточно для динамического выделения выровненных векторов __m128
, и вам нужно выйти за рамки простого вызова new
или malloc
.
Статическое и автоматическое хранение легко: используйте alignas(32) float arr[N];
C++ 17 предоставляет выровненный new
для выровненного динамического выделения, совместимого с delete
:
float * arr = new (std::align_val_t(32)) float[numSteps];
См. документацию для new
/new[]
и std::align_val_t
Другие параметры динамического выделения в основном совместимы с malloc
/free
, а не с new
/delete
:
std::aligned_alloc
: ISO C++ 17. основной недостаток: размер должен быть кратен выравниванию. Это требование мозговой смерти делает его неуместным для выделения, например, выровненного массива строки кэша 64B с неизвестным числомfloat
. Или, в частности, выровненный массив 2M, чтобы воспользоваться прозрачными огромными страницами.Версия C
aligned_alloc
была добавлена в ISO C11. Он доступен в некоторых, но не во всех компиляторах C++. Как отмечалось на странице cppreference, версия C11 не должна была терпеть неудачу, когда размер не кратен выравниванию (это неопределенное поведение), поэтому многие реализации предоставили очевидное желаемое поведение как "расширение". В настоящее время ведутся обсуждения, чтобы исправить это, но пока я не могу порекомендоватьaligned_alloc
в качестве переносимого способа выделения массивов произвольного размера.Кроме того, комментаторы сообщают, что он недоступен в MSV C++. Смотрите лучший кроссплатформенный метод для получения выровненной памяти для жизнеспособного
#ifdef
для Windows. Но в AFAIK отсутствуют функции выравнивания с выравниванием в Windows, которые создают указатели, совместимые со стандартомfree
.posix_memalign
: часть стандарта POSIX 2001, а не стандарта ISO C или C++. Неуклюжий прототип/интерфейс по сравнению сaligned_alloc
. Я видел, как gcc генерирует перезагрузки указателя, потому что он не был уверен, что хранилища в буфере не изменили указатель. (Посколькуposix_memalign
передается адрес указателя.) Поэтому, если вы используете это, скопируйте указатель в другую переменную C++, чей адрес не был передан вне функции.
#include <stdlib.h>
int posix_memalign(void **memptr, size_t alignment, size_t size); // POSIX 2001
void *aligned_alloc(size_t alignment, size_t size); // C11 (and ISO C++17)
_mm_malloc
: доступно на любой платформе, где доступен_mm_whatever_ps
, но вы не можете передавать от него указатели наfree
. На многих C и C++ реализациях_mm_free
иfree
совместимы, но они не гарантированно переносимы. (И в отличие от двух других, он не будет работать во время выполнения, а не во время компиляции.) В MSVC в Windows_mm_malloc
использует_aligned_malloc
, что несовместимо сfree
; на практике это дает сбой.
В C++ 11 и более поздних версиях: используйте alignas(32) float avx_array[1234]
в качестве первого члена члена структуры/класса (или непосредственно для простого массива), чтобы статические и автоматические объекты хранения этого типа имели выравнивание 32B. В документации std::aligned_storage
есть пример этой техники, объясняющий, что делает std::aligned_storage
.
Это на самом деле не работает для динамически распределенного хранилища (например, std::vector<my_class_with_aligned_member_array>
), см. Как заставить std::vector выделять выровненную память.
В C++ 17 может быть способ использовать выровненный новый для std::vector
. ТОДО: узнай как.
И, наконец, последний вариант настолько плох, что даже не входит в список: выделите больший буфер и добавьте do p+=31; p&=~31ULL
с соответствующим приведением. Слишком много недостатков (трудно освободить, тратить память) стоит обсудить, поскольку функции выравнивания-распределения доступны на каждой платформе, поддерживающей встроенные функции Intel _mm256
. Но есть даже библиотечные функции, которые помогут вам сделать это, IIRC.
Требование использовать _mm_free
вместо free
, вероятно, существует для возможности реализации _mm_malloc
поверх простого старого malloc
с использованием этой техники.