繁体   English   中英

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

[英]Custom STL Allocator: Construct/Destroy called for each element

我有这个代码片段

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;

Output:

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 元素都会调用构造/销毁,这导致执行时间增加了 20 倍。 我怎样才能防止这种基本类型的这种行为?

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

这些函数只需要那么多时间,因为您正在修改其中的 static。 此外,您的实施没有执行他们需要做的工作。 construct ,如果你声明它,必须通过一种或另一种方式在 memory 中构造一个T类型的 object 。 并且destroy必须调用析构函数,至少对于没有平凡析构函数的类型。

也就是说, std::vector<char> myBuffer(20e6); std::vector<char, HeapAllocator<char>>myCustomBuffer(20e6); 仍然需要一些时间,因为它们将分配的 memory 归零。 std::vector不提供任何接口使其处于未初始化状态,并且在一定程度上依赖编译器识别std::construct_at循环可以优化为memset (对于std::allocator ,标准库在看到使用std::allocator时可以直接专门化为memset 。)

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


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

  • auto p = new T[n]; 是错的。 allocate应该分配 memory,而不是构造对象。 在对乘法进行溢出检查后,您应该只分配适当大小的 memory 和 alignment,例如使用::operator new[](n*sizeof(T), std::align_val_t{alignof(T)}) 类似地, deallocate应该使用匹配的 deallocation function(例如::operator delete[] )。 除此之外, deletenew[]也与未定义的行为不匹配。 new[]之后你必须使用delete[] ,而不是delete

  • 您的 class 缺少operator== (和operator!=在 C++20 之前)。 它们是必需的,如果您的 class 没有任何非静态数据成员并且没有定义is_always_equal成员,则operator==必须始终返回true并且operator!=始终返回false

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM