我有这个代码片段
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==
必须始终返回true
,operator!=
始终返回false
。