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

#c #c 11 #c 14 #c 17

#c #управление памятью #выравнивание памяти #распределитель

Вопрос:

Возможно ли сделать std::vector из пользовательских структур выделение выровненной памяти для дальнейшей обработки с помощью инструкций SIMD? Если это возможно сделать с Allocator , есть ли у кого-нибудь такой распределитель, которым он мог бы поделиться?

Комментарии:

1. вы проверили, делает ли это уже стандартный распределитель для вас?

2. @rhalbersma: Я не думаю, что это так, он не принимает параметр выравнивания.

3. нет, я имею в виду: ваша реализация STL уже выравнивает память для вас? Вы вычислили адрес памяти v.begin() и проверили, начинается ли он с кратного X байтов? даже если вы не можете явно настроить выравнивание, std::allocator уже может помочь вам с этим.

4. @VioletGiraffe: более вероятно, что оно выравнивается по 8-байтовой границе.

5. Обратите внимание, что с C 17 std::vector<__m256> автоматически выделяется память с выравниванием в 32 байта 🙂

Ответ №1:

Редактировать: я удалил наследование std::allocator , как было предложено GManNickG, и сделал параметр выравнивания параметром времени компиляции.

Недавно я написал этот фрагмент кода. Это тестируется не так часто, как хотелось бы, поэтому продолжайте и сообщайте об ошибках. 🙂

 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 Tamp;        reference;
    typedef const Tamp;  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>amp;) 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, Argsamp;amp;... 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 Tamp;  reference;
    typedef const Tamp;  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>amp;) 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, Argsamp;amp;... 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>amp;, const AlignedAllocator<U, UAlign>amp;) noexcept
{ return TAlign == UAlign; }

template <typename T, Alignment TAlign, typename U, Alignment UAlign>
inline
bool
operator!= (const AlignedAllocator<T,TAlign>amp;, const AlignedAllocator<U, UAlign>amp;) noexcept
{ return TAlign != UAlign; }
  

Реализация для фактических вызовов распределения выполняется только в posix, но вы можете легко расширить это.

 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(amp;ptr, align, size);

    if (rc != 0) {
        return nullptr;
    }

    return ptr;
}


void
detail::deallocate_aligned_memory(void *ptr) noexcept
{
    return free(ptr);
}
  

Нужен C 11, кстати.

Комментарии:

1. Я не думаю, что вам нужно или следует наследовать от <strike> std::exception<> </strike> std::allocator<> .

2. @GManNickG, возможно, ты имел в виду allocator ? 🙂

3. @avakar: Ого, даже не заметил, что я это написал!

4. Старый был лучше, я никак не могу скомпилировать это в VS 2010 🙂

5. Не определено nail::is_power_of_two

Ответ №2:

В предстоящей версии 1.56 библиотека Boost будет включать Boost.Выровнять. Среди других вспомогательных средств выравнивания памяти он предоставляет boost::alignment::aligned_allocator , которые могут быть использованы в качестве замены для std::allocator и позволяют указать выравнивание. Смотрите документацию по https://boostorg.github.io/align /

Комментарии:

1. Приятно знать, но лично мне boost довольно сложно интегрировать в свои проекты (те библиотеки, которые не предназначены только для заголовков).

2.Я согласен, интеграция boost может быть немного болезненной. Однако, Boost.Align это только для заголовков, а также зависит только от других библиотек AFAICS, предназначенных только для заголовков.

3. Теперь это доступно: boost.org/doc/libs/1_56_0/libs/core/doc/html/index.html

Ответ №3:

Начиная с C 17, просто используйте std::vector<__m256i> или с любым другим выровненным типом. Существует выровненная версия operator new , она используется std::allocator для выровненных типов (а также с помощью простого new выражения, поэтому new __m256i[N] также безопасен, начиная с C 17).

В комментарии @MarcGlisse говорится об этом, что делает этот ответ более заметным.

Ответ №4:

Да, это должно быть возможно. Если вы зададите этот вопрос в Google, то получите множество примеров кода, ниже приведены некоторые многообещающие результаты:

https://bitbucket.org/marten/alignedallocator/wiki/Home

http://code.google.com/p/mastermind-strategy/source/browse/trunk/src/util/aligned_allocator.hpp?r=167

https://gist.github.com/1471329