Nowoczesne podejście do przydzielania wyrównanej pamięci std :: vector

11

Następujące pytanie związane jest jednak odpowiedzi są stare, i komentarz od użytkownika Marc Glisse sugeruje nowych podejść od C ++ 17 do tego problemu, które nie mogą być należycie rozpatrzone.

Próbuję uzyskać wyrównaną pamięć działającą poprawnie dla SIMD, wciąż mając dostęp do wszystkich danych.

W przypadku Intela, jeśli utworzę wektor zmiennoprzecinkowy typu __m256i zmniejszy mój rozmiar 8-krotnie, otrzymam wyrównaną pamięć.

Na przykład std::vector<__m256> mvec_a((N*M)/8);

W nieco zhakowany sposób mogę rzutować wskaźniki na elementy wektorowe w celu unoszenia się na powierzchni, co pozwala mi na dostęp do poszczególnych wartości zmiennoprzecinkowych.

Zamiast tego wolałbym mieć std::vector<float>poprawnie wyrównany, a zatem może być ładowany do __m256innych typów SIMD bez segfault.

Patrzyłem na wyrównany_alloc .

To może dać mi tablicę w stylu C, która jest poprawnie wyrównana:

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

Nie jestem jednak pewien, jak to zrobić std::vector<float>. Przekazanie std::vector<float>własności marr_a nie wydaje się możliwe .

Widziałem kilka sugestii, że powinienem napisać niestandardowy alokator , ale wydaje się, że to dużo pracy, a może w nowoczesnym C ++ jest lepszy sposób?

Prunus Persica
źródło
1
bez segfaultingu ... lub bez potencjalnego spowolnienia podziału linii pamięci podręcznej podczas korzystania _mm256_loadu_ps(&vec[i]). (Chociaż uwaga, że przy domyślnych opcjach tuningu, GCC dzieli nie gwarantowanych wyrównany 256-bitowe Obciążenia / sklepy w vmovups xmm / vinsertf128. Więc nie jest zaletą przy użyciu _mm256_loadponad loadujeśli dbasz o to jak kompiluje kod na GCC, jeśli ktoś zapomni użycie -mtune=...lub -march=opcje.)
Peter Cordes

Odpowiedzi:

1

Wszystkie kontenery w standardowej bibliotece C ++, w tym wektory, mają opcjonalny parametr szablonu, który określa alokator kontenera , a implementacja własnego nie jest naprawdę duża:

class my_awesome_allocator {
};

std::vector<float, my_awesome_allocator> awesomely_allocated_vector;

Będziesz musiał napisać trochę kodu, który implementuje twój alokator, ale nie byłby to dużo więcej kodu niż już napisałeś. Jeśli nie potrzebujesz obsługi wersji wcześniejszej niż C ++ 17, musisz jedynie zaimplementować metody replaceate () i deallocate () , to wszystko.

Sam Varshavchik
źródło
Muszą także się specjalizowaćallocator_traits
NathanOliver
1
To może być dobre miejsce na kanoniczną odpowiedź z przykładem, że ludzie mogą kopiować / wklejać, aby przeskakiwać irytujące obręcze C ++. (Punkty bonusowe, jeśli istnieje sposób, aby std :: vector spróbowało ponownie przydzielić w miejsce zamiast zwykłego braindead C ++ zawsze alokuj + kopiuj.) Należy również pamiętać, że vector<float, MAA>nie jest to zgodne z typem vector<float>(i nie może być, ponieważ cokolwiek, co działa .push_backna zwykłym std::vector<float>kompilowanym bez tego alokatora, może dokonać nowego przydziału i skopiować go do minimalnie wyrównanej pamięci. A nowe / usuń nie jest zgodne z wyrównanym_alokiem / wolne)
Peter Cordes
1
Nie sądzę, że istnieje jakakolwiek gwarancja, że ​​wskaźnik zwrócony z alokatora jest bezpośrednio używany jako adres podstawowy std::vectortablicy. Na przykład mogę sobie wyobrazić implementację std::vectorużycia tylko jednego wskaźnika do przydzielonej pamięci, która przechowuje koniec / pojemność / alokator w pamięci przed zakresem wartości. To z łatwością może udaremnić wyrównanie wykonane przez alokatora.
Dietmar Kühl
1
Tyle że std::vectorto gwarantuje. Do tego używa. Być może powinieneś przejrzeć to, co określa tutaj standard C ++.
Sam Varshavchik
1
> Muszą także się specjalizować allocator_traits- Nie, nie robią tego. Wystarczy zaimplementować zgodny alokator.
Andrey Semashev