Ну, во-первых, векторы SIMD и std::vector
не имеют ничего общего друг с другом. Я знаю, вы уже знаете это, но будущие читатели должны тщательно подумать, действительно ли это то, что они хотят делать.
Это безопасно; .data()
должен возвращать указатель, который может быть прочитан или записан с любым допустимым индексом . Это, безусловно, безопасно на практике, учитывая детали реализации реальных библиотек std::vector
. И я абсолютно уверен в том, что касается бумажных стандартов.
Судя по комментариям, вас беспокоит строгий псевдоним UB.
Чтение / запись других объектов с помощью may_alias
типов указателей (включая char*
или __m256i*
) - это нормально. memcpy(&a, &b, sizeof(a))
является типичным примером изменения представления объекта a
с помощью char*
. В самой memcpy нет ничего особенного; это хорошо определено из-за особого случая char*
aliasing.
may_alias
- это расширение GNU C, которое позволяет вам определять типы, отличные от char
, которым разрешено создавать псевдонимы, как char*
. Определение __m128
/ __m256i
в GNU C относится к собственным векторам GNU C, таким как typedef long long __m256i __attribute((vector_size(32), may_alias));
. Другие реализации C ++ (например, MSVC) по-разному определяют __m256i
, но API-интерфейс Intel intrinsics гарантирует, что псевдонимы-указатели на векторы на другие типы является законным в любом случае, где char*
/ memcpy
будет.
См. Также Является ли `reinterpret_cast` между указателем аппаратного вектора и соответствующим типом неопределенным поведением?
Также: SSE: Разница между _mm_load / store и использованием прямого доступа к указателю - loadu
/ storeu
подобна приведению версии aligned(1)
векторного типа перед разыменованием. Таким образом, все эти рассуждения об указателях и псевдонимах относятся к передаче указателя на _mm_storeu
, а не только к разыменованию напрямую.
Идиоматические; ну конечно, это выглядит довольно идиоматическим C ++. Я мог бы все еще использовать приведение в стиле C с внутренними объектами только потому, что reinterpret
слишком долго для чтения, и плохо разработанный API встроенных функций для целочисленных векторов нуждается во всем. Возможно, будет уместна шаблонная функция-обертка для si256 load / loadu и store / storeu, которая приводит к __m256i*
или const __m256i*
из любого типа указателя.
Я мог бы предпочесть что-то, что передало элементы __m256i
конструктору out
, однако, чтобы не дать глупым компиляторам потенциально обнулить память и затем сохранить вектор. Но, надеюсь, этого не произойдет.
На практике gcc и clang оптимизируют мертвые хранилища до нуля 8 элементов перед сохранением вектора. Любая попытка использовать конструктор итератора vector(begin, end)
вместо этого усугубляет ситуацию, добавляя дополнительный код для обработки исключений поверх хранилища / перезагрузки in
в стек (около new
), а затем сохраняя его во вновь выделенном месте. память.
Посмотрите некоторые попытки в проводнике компилятора Godbolt , обратите внимание, что они сохраняют / восстанавливают r13
там, где версия @ Bee не делает, а также имеют дополнительный код, сгенерированный вне обычного пути через функцию. Это уходит с -fno-exceptions
, но тогда они просто равны, не лучше, чем версия @ Bee. Так что используйте код в вопросе; он компилируется по крайней мере так же, как и любая из моих попыток отличаться.
Я мог бы также предпочесть сделать что-нибудь, чтобы получить новый std::vector<uint32_t>
, выделенный с 32-байтовой выровненной памятью, если это возможно без изменения типа шаблона. Я не уверен, возможно ли это .
Даже если бы мы могли просто выровнять это начальное распределение на практике, не меняя тип, чтобы сделать это гарантией времени компиляции для будущего использования, это потенциально могло бы помочь. AVX-код, который оставляет невыровненную обработку HW, выиграл бы от отсутствия разбиения строки кэша.
Но я не думаю, что это возможно и без взлома пользовательского конструктора для std::vector
, который выполняет начальное выделение с выровненным new
, при условии, что он совместим с обычным delete
.
Если вы можете использовать std::vector<uint32_t, some_aligned_allocator>
повсюду в своем коде, то может стоить того.Но, вероятно, не стоит беспокоиться, если вам придется передать его в код, который использует обычный vector<uint32_t>
.
Вы могли бы лгать вашему компилятору, потому что этот тип является двоично-совместимым (но не исходным)- совместимо) с обычным std::vector<uint32_t>
, в системах, где выровненный новый / удалить совместим с обычным новым / удалить.Но я не рекомендую это.