I have this code snippet
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
With this allocator
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;
So it's obvious that construct/destroy are called for each char element of the buffer which results in 20x in execution time compared to default allocator. How can I prevent this behavior for this basic types?
You simply do not declare them at all. They will be defaulted through std::allocator_traits
to use std::construct_at
/ std::destroy_at
(ie a placement-new and (pseudo-)destructor call) instead, just as for std::allocator
. This applies since C++11.
The functions are only taking so much time because you are modifying a static in them. Also, your implementations are not performing the job they need to do. construct
, if you declare it, must construct an object of type T
in the memory via a placement-new one way or another. And destroy
must call the destructor, at least for types without trivial destructor.
That said, std::vector<char> myBuffer(20e6);
and std::vector<char, HeapAllocator<char>>myCustomBuffer(20e6);
will still take some time since they zero the allocated memory. std::vector
doesn't offer any interface to leave it uninitialized and there is some dependence on the compiler recognizing that the std::construct_at
loop can be optimized to a memset
. (For std::allocator
the standard library can simply specialize to memset
directly when it sees std::allocator
is used.)
However, std::vector
offers .reserve
instead which will reserve enough memory through allocate
without constructing any objects in it. Objects can then be created with push_back
/ emplace_back
/ resize
as required later.
Also your allocator has some other issues:
auto p = new T[n];
is wrong. allocate
is supposed to allocate memory, not to construct objects. You should be only allocating memory of appropriate size and alignment, eg with ::operator new[](n*sizeof(T), std::align_val_t{alignof(T)})
, after an overflow check on the multiplication. Similarly deallocate
should then use the matching deallocation function (eg ::operator delete[]
). That aside delete
and new[]
are a mismatch with undefined behavior as well. After new[]
you must use delete[]
, not delete
.
Your class lacks operator==
(and operator!=
before C++20). They are required and if your class doesn't have any non-static data members and doesn't define a is_always_equal
member, then operator==
must always return true
and operator!=
always false
.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.