Современный подход к созданию std :: vector для выравнивания памяти

11

Следующий вопрос связан, однако ответы на старые, и комментарий от пользователя Marc Glisse предполагает , что есть новые подходы , начиная с C ++ 17 к решению этой проблемы , которые не могут быть адекватно обсуждены.

Я пытаюсь выровнять память, работающую правильно для SIMD, при этом все еще имея доступ ко всем данным.

В Intel, если я создаю вектор типа float __m256и уменьшаю свой размер в 8 раз, это дает мне выровненную память.

Например std::vector<__m256> mvec_a((N*M)/8);

Немного хакерским способом я могу приводить указатели на векторные элементы для плавания, что позволяет мне получать доступ к отдельным значениям плавания.

Вместо этого я бы предпочел иметь std::vector<float>правильно выровненный, и, следовательно, может быть загружен в __m256и другие типы SIMD без segfaulting.

Я искал в align_alloc .

Это может дать мне массив в стиле C, который выровнен правильно:

auto align_sz = static_cast<std::size_t> (32);
float* marr_a = (float*)aligned_alloc(align_sz, N*M*sizeof(float));

Однако я не уверен, как это сделать std::vector<float>. Предоставление std::vector<float>владения marr_a не представляется возможным .

Я видел несколько предложений о том, что мне следует написать собственный распределитель , но это похоже на большую работу, и, возможно, с современным C ++ есть лучший способ?

Prunus Persica
источник
1
без сегментации ... или без потенциальных замедлений из-за разделения строк кэша при использовании _mm256_loadu_ps(&vec[i]). (Несмотря на то, обратите внимание , что с параметрами настройки по умолчанию, GCC расщепляется не гарантируемой выровнен 256-битовые нагрузок / магазинов в vmovups XMM / vinsertf128. Так что это преимущество для использования _mm256_loadболее , loaduесли вы заботитесь о том , как ваш код компилируется на GCC если кто - то забывает использовать -mtune=...или -march=варианты.)
Питер Кордес

Ответы:

1

Все контейнеры в стандартной библиотеке C ++, включая векторы, имеют необязательный параметр шаблона, который задает распределитель контейнера , и на самом деле его реализация не так уж и сложна:

class my_awesome_allocator {
};

std::vector<float, my_awesome_allocator> awesomely_allocated_vector;

Вам придется написать немного кода, который реализует ваш распределитель, но он не будет намного больше кода, чем вы уже написали. Если не требуется предварительно C ++ 17 поддержки вам нужно только реализовать выделить () и DEALLOCATE () методы, это все .

Сэм Варшавчик
источник
Они также должны специализироватьсяallocator_traits
NathanOliver
1
Это может быть хорошим местом для канонического ответа с примером, что люди могут копировать / вставлять, чтобы перепрыгивать через раздражающие обручи C ++. (Бонусные баллы, если есть способ позволить std :: vector пытаться перераспределить на месте вместо обычного braindead C ++ всегда alloc + copy.) Также, конечно, обратите внимание, что это vector<float, MAA>несовместимо с типом vector<float>(и не может быть, потому что все, что делает .push_backна простой std::vector<float>скомпилированной без этого распределителя, может сделать новое распределение и скопировать в минимально выровненную память. И new / delete не совместим с align_alloc / free)
Питер Кордес
1
Я не думаю, что есть какая-либо гарантия, что указатель, возвращенный из распределителя, напрямую используется в качестве базового адреса std::vectorмассива. Например, я мог бы представить реализацию std::vectorиспользования только одного указателя на выделенную память, которая хранит конец / емкость / распределитель в памяти до диапазона значений. Это может легко помешать выравниванию, выполненному распределителем.
Дитмар Кюль
1
За исключением того, что std::vectorэто гарантирует. Вот для чего он это использует. Возможно, вам следует рассмотреть то, что стандарт C ++ определяет здесь.
Сэм Варшавчик
1
> Они также должны специализироваться allocator_traits- нет, они этого не делают. Все, что нужно, это реализовать совместимый распределитель.
Андрей Семашев