自定义STL分配器:为每个元素调用构造/销毁



我有这个代码片段

auto start = high_resolution_clock::now();
std::vector<char> myBuffer(20e6);
std::cout << "StandardAlloc Time:" << duration_cast<milliseconds>(high_resolution_clock::now() - start).count() << std::endl;
start = high_resolution_clock::now();
std::vector<char, HeapAllocator<char>>myCustomBuffer(20e6);
std::cout << "CustomAlloc Time:" << duration_cast<milliseconds>(high_resolution_clock::now() - start).count() << " CC: " <<  HeapAllocator<char>::constructCount << std::endl;

输出:

StandardAlloc Time:6
CustomAlloc Time:124 CC: 20000000

有了这个分配器

template<class T>
struct HeapAllocator
{
typedef T value_type;
HeapAllocator(){};
template<class U>
constexpr HeapAllocator(const HeapAllocator<U>&) noexcept {}
[[nodiscard]] T* allocate(std::size_t n)
{
auto p = new T[n];
return p;
}
void deallocate(T* p, std::size_t n) noexcept
{
delete p;
}
template <class U>
void destroy(U* p)
{
destroyCount++;
}
template< class U, class... Args >
void construct(U* p, Args&&... args)
{
constructCount++;
}
static int destroyCount;
static int constructCount;
};
template<class T>
int HeapAllocator<T>::constructCount = 0;

因此,很明显,为缓冲区的每个char元素调用construct/destroy,与默认分配器相比,这将导致20倍的执行时间。如何防止这种基本类型的这种行为?

您根本不需要声明它们。它们将通过std::allocator_traits默认使用std::construct_at/std::destroy_at(即放置新的和(伪)析构函数调用),就像std::allocator一样。这适用于C++11之后的版本。

这些函数只需要花费这么多时间,因为您正在修改其中的静态。此外,您的实现没有执行它们需要做的工作。如果您声明construct,则必须通过新的放置方式在内存中构造T类型的对象。并且destroy必须调用析构函数,至少对于没有平凡析构函数的类型是这样。

也就是说,std::vector<char> myBuffer(20e6);std::vector<char, HeapAllocator<char>>myCustomBuffer(20e6);仍将花费一些时间,因为它们将分配的内存归零。std::vector不提供任何未初始化的接口,并且在某种程度上依赖于编译器识别出std::construct_at循环可以优化为memset。(对于std::allocator,当看到使用了std::allocator时,标准库可以简单地直接专门化为memset。)

然而,std::vector提供了.reserve,它将通过allocate保留足够的内存,而不在其中构建任何对象。然后,可以根据需要使用push_back/emplace_back/resize创建对象。


此外,您的分配器还有一些其他问题:

  • auto p = new T[n];错误。allocate应该分配内存,而不是构造对象。在对乘法进行溢出检查后,您应该只分配适当大小和对齐的内存,例如使用::operator new[](n*sizeof(T), std::align_val_t{alignof(T)})。类似地,deallocate然后应该使用匹配的解除分配函数(例如::operator delete[])。除了CCD_ 27和CCD_。在new[]之后,必须使用delete[],而不是delete

  • 您的类缺少operator==(以及C++20之前的operator!=)。它们是必需的,如果您的类没有任何非静态数据成员,也没有定义is_always_equal成员,那么operator==必须始终返回trueoperator!=始终返回false

最新更新