Преобразование в и из __m256i и std :: vector - PullRequest
3 голосов
/ 24 июня 2019

Я хочу преобразовать в __m256i экземпляры и std::vector<uint32_t> экземпляры (содержащие ровно 8 элементов).

Пока я придумал следующее:

using vu32 = std::vector<uint32_t>;

__m256i v2v(const vu32& in) {
    assert(in.size() == 8);
    return _mm256_loadu_si256(reinterpret_cast<const __m256i*>(in.data()));
}

vu32 v2v(__m256i in) {
    vu32 out(8);
    _mm256_storeu_si256(reinterpret_cast<__m256i*>(out.data()), in);
    return out;
}

Isэто безопасно?

Есть ли более идиоматический способ сделать это?

1 Ответ

2 голосов
/ 27 июня 2019

Ну, во-первых, векторы 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>, в системах, где выровненный новый / удалить совместим с обычным новым / удалить.Но я не рекомендую это.

...