Is it possible to make std::vector
of custom structs allocate aligned memory for further processing with SIMD instructions? If it is possible to do with Allocator
, does anyone happen to have such an allocator he could share?
An std::vector manages its own memory. You can use the reserve() and resize() methods to have it allocate enough memory to fit a given amount of items: std::vector<int> vec1; vec1. reserve(30); // Allocate space for 30 items, but vec1 is still empty.
So there is no surprise regarding std::vector. It uses 4 bytes to store each 4 byte elements. It is very efficient.
The aligned_alloc function allocates space for an object whose alignment is specified by alignment, whose size is specified by size, and whose value is indeterminate.
Vectors are assigned memory in blocks of contiguous locations. When the memory allocated for the vector falls short of storing new elements, a new memory block is allocated to vector and all elements are copied from the old location to the new location.
Edit: I removed the inheritance of std::allocator
as suggested by GManNickG and made the alignment parameter a compile time thing.
I recently wrote this piece of code. It's not tested as much as I would like it so go on and report errors. :-)
enum class Alignment : size_t { Normal = sizeof(void*), SSE = 16, AVX = 32, }; namespace detail { void* allocate_aligned_memory(size_t align, size_t size); void deallocate_aligned_memory(void* ptr) noexcept; } template <typename T, Alignment Align = Alignment::AVX> class AlignedAllocator; template <Alignment Align> class AlignedAllocator<void, Align> { public: typedef void* pointer; typedef const void* const_pointer; typedef void value_type; template <class U> struct rebind { typedef AlignedAllocator<U, Align> other; }; }; template <typename T, Alignment Align> class AlignedAllocator { public: typedef T value_type; typedef T* pointer; typedef const T* const_pointer; typedef T& reference; typedef const T& const_reference; typedef size_t size_type; typedef ptrdiff_t difference_type; typedef std::true_type propagate_on_container_move_assignment; template <class U> struct rebind { typedef AlignedAllocator<U, Align> other; }; public: AlignedAllocator() noexcept {} template <class U> AlignedAllocator(const AlignedAllocator<U, Align>&) noexcept {} size_type max_size() const noexcept { return (size_type(~0) - size_type(Align)) / sizeof(T); } pointer address(reference x) const noexcept { return std::addressof(x); } const_pointer address(const_reference x) const noexcept { return std::addressof(x); } pointer allocate(size_type n, typename AlignedAllocator<void, Align>::const_pointer = 0) { const size_type alignment = static_cast<size_type>( Align ); void* ptr = detail::allocate_aligned_memory(alignment , n * sizeof(T)); if (ptr == nullptr) { throw std::bad_alloc(); } return reinterpret_cast<pointer>(ptr); } void deallocate(pointer p, size_type) noexcept { return detail::deallocate_aligned_memory(p); } template <class U, class ...Args> void construct(U* p, Args&&... args) { ::new(reinterpret_cast<void*>(p)) U(std::forward<Args>(args)...); } void destroy(pointer p) { p->~T(); } }; template <typename T, Alignment Align> class AlignedAllocator<const T, Align> { public: typedef T value_type; typedef const T* pointer; typedef const T* const_pointer; typedef const T& reference; typedef const T& const_reference; typedef size_t size_type; typedef ptrdiff_t difference_type; typedef std::true_type propagate_on_container_move_assignment; template <class U> struct rebind { typedef AlignedAllocator<U, Align> other; }; public: AlignedAllocator() noexcept {} template <class U> AlignedAllocator(const AlignedAllocator<U, Align>&) noexcept {} size_type max_size() const noexcept { return (size_type(~0) - size_type(Align)) / sizeof(T); } const_pointer address(const_reference x) const noexcept { return std::addressof(x); } pointer allocate(size_type n, typename AlignedAllocator<void, Align>::const_pointer = 0) { const size_type alignment = static_cast<size_type>( Align ); void* ptr = detail::allocate_aligned_memory(alignment , n * sizeof(T)); if (ptr == nullptr) { throw std::bad_alloc(); } return reinterpret_cast<pointer>(ptr); } void deallocate(pointer p, size_type) noexcept { return detail::deallocate_aligned_memory(p); } template <class U, class ...Args> void construct(U* p, Args&&... args) { ::new(reinterpret_cast<void*>(p)) U(std::forward<Args>(args)...); } void destroy(pointer p) { p->~T(); } }; template <typename T, Alignment TAlign, typename U, Alignment UAlign> inline bool operator== (const AlignedAllocator<T,TAlign>&, const AlignedAllocator<U, UAlign>&) noexcept { return TAlign == UAlign; } template <typename T, Alignment TAlign, typename U, Alignment UAlign> inline bool operator!= (const AlignedAllocator<T,TAlign>&, const AlignedAllocator<U, UAlign>&) noexcept { return TAlign != UAlign; }
The implementation for the actual allocate calls is posix only but you can extent that easily.
void* detail::allocate_aligned_memory(size_t align, size_t size) { assert(align >= sizeof(void*)); assert(nail::is_power_of_two(align)); if (size == 0) { return nullptr; } void* ptr = nullptr; int rc = posix_memalign(&ptr, align, size); if (rc != 0) { return nullptr; } return ptr; } void detail::deallocate_aligned_memory(void *ptr) noexcept { return free(ptr); }
Needs C++11, btw.
In the upcoming version 1.56, the Boost library will include Boost.Align. Among other memory alignment helpers it provides boost::alignment::aligned_allocator
, which can be used a drop-in replacement for std::allocator
and allows you to specify an alignment. See the documentation on https://boostorg.github.io/align/
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With