allocator、polymorphic allocator 与 memory_resource
allocator、polymorphic allocator 与 memory_resource
- Created: 2024-07-04T10:59+08:00
- Published: 2024-07-05T11:27+08:00
- Categories: C-CPP
custom allocator
std::allocator
是无状态的,实测最简单的 allocator 只需要:
- value_type
- allocate
- deallocate
rebind 目的是实现 rebind(allocator<TypeA>, TypeB) == allocator<TypeB>
C++11 已经使用 allocator_traits 实现了这种想法[1],并且 C++17 就抛弃了以前把 rebind 放到 allocator 内部实现的方法。
还有两个函数 construct
和 destroy
,如果提供,就会使用我们自定义的,不提供也没有问题,allocator_traits 提供了默认的实现,
container 本身就是通过 allocator_traits 来使用 construct 和 destroy。
- construct:在 allocate 后调用,在分配的内存上初始化对象
- destroy: 在 deallocate 前调用,在内存上析构分配的对象
以下是一个直接包装了 ::new
和 ::delete
的 allocator 实现。
#include <vector>
#include <iostream>
#include <memory>
using std::vector;
struct point
{
int x{1};
point(int x_) : x(x_) {}
~point()
{
std::cout << "detor, x:" << x << std::endl;
}
};
template <typename T>
class MyAllocator
{
public:
using value_type = T;
T *allocate(size_t n)
{
std::cout << "myallocator.allocate: " << n << std::endl;
return (T *)::operator new(n * sizeof(T));
}
void deallocate(T *p, size_t n)
{
std::cout << "myallocator.deallocate: " << n << std::endl;
return ::operator delete(p);
}
// template <typename _Up, typename... _Args>
// void construct(_Up *__p, _Args &&...__args) noexcept(noexcept(::new((void *)__p) _Up(std::forward<_Args>(__args)...)))
// {
// std::cout << "construct called" << std::endl;
// // 表示在 地址 _p 上调用对象 _Up的构造函数
// // 其中,__args是构造函数的参数
// ::new ((void *)__p) _Up(std::forward<_Args>(__args)...);
// }
// template <typename _Up>
// void destroy(_Up *__p) noexcept(noexcept(__p->~_Up()))
// {
// std::cout << "destroy called" << std::endl;
// __p->~_Up();
// }
};
int main()
{
point a{2};
vector<point, MyAllocator<point>> vp{3,4,5};
vp[0].x = 100;
for (auto& i: vp) {
std::cout << i.x << std::endl;
}
return 0;
}
多态内存资源(PMR, polymorphic memory resource)
使用 PMR 原因
为什么需要 PMR 呢,因为[2]:
- allocator 是模板签名的一部分。不同 allocator 的容器,无法混用。
- c++11 以前,allocator 无状态;c++11 以后,可以有状态,然而 allocator 类型复杂难用。
- allocator 内存对齐无法控制,需要传入自定义 allocator。
以上三点、特别是第一点,造成 stl 无法成为软件接口 (interface) 的一部分。
难以将 memory arena、memory pool 用于 stl 容器。
比如自定义的 allocator 没法和 stl 默认的 allocator 通用:
vector<int, MyAllocator<int>> vi {1,2,3};
vector<int, std::allocator<int>> vi_copy = vi; // error!
memory_resource 和 polymorphic_allocator
PMR 就说,好吧,那我们把 allocator 固定下来,全都使用 polymorphic_allocator<T>
,polymorphic_allocator<T>
持有一根 memory_resource
的指针[3],分配策略通过 memory_resource 实现。
The class template std::pmr::polymorphic_allocator is an Allocator which exhibits different allocation behavior depending upon the std::pmr::memory_resource from which it is constructed. Since memory_resource uses runtime polymorphism to manage allocations, different container instances with polymorphic_allocator as their static allocator type are interoperable, but can behave as if they had different allocator types.
All specializations of polymorphic_allocator meet the allocator completeness requirements.
memory_resource
提供对原始内存的管理接口,类似 malloc 和 free:
memory_resource:
+ allocate: 提供给 polymorphic_allocator,调用 do_allocate
+ deallocate:提供给 polymorphic_allocator,调用 do_deallocate
# do_allocate: 内部分配内存的方法,像 malloc
# do_deallocate: 内部回收内存的方法,像 free
polymorphic_allocator<T>
只是在原始内存 memory_resource
上提供具体类型的抽象,比如需要 n 个类型为 T 的对象,底层调用 memory_resource 获取原始内存。
std::pmr::polymorphic_allocator<T>::allocate
:
Allocates storage for n objects of type T using the underlying memory resource. Equivalent to return static_cast<T*>(resource()->allocate(n * sizeof(T), alignof(T)));.
std::pmr::polymorphic_allocator::allocate - cppreference.com
construct 和 destroy 通过 allocator_traits 实现:
/// Partial specialization for std::pmr::polymorphic_allocator
template<typename _Tp>
struct allocator_traits<pmr::polymorphic_allocator<_Tp>>
现成的 memory_resource
上面提到的分离接口可以实现不同 allocator 之间的通用,但是具体要让内存分配快起来需要高效的 memory_resource 实现,C++17 提供了五种[4]:
内存资源 | 行为 |
---|---|
new_delete_resource() |
返回一个调用new 和delete 的内存资源的指针 |
synchronized_pool_resource |
创建更少碎片化的、线程安全的内存资源的类 |
unsynchronized_pool_resource |
创建更少碎片化的、线程不安全的内存资源的类 |
monotonic_buffer_resource |
创建从不释放、可以传递一个可选的缓冲区、线程不安全的类 |
null_memory_resource() |
返回一个每次分配都会失败的内存资源的指针 |
这三种比较重要:
- std::pmr::monotonic_buffer_resource - cppreference.com
- std::pmr::synchronized_pool_resource - cppreference.com
- std::pmr::unsynchronized_pool_resource - cppreference.com
在接口调用中提到了 upstream 的概念:如果当前 memory_resource 内存不足,则调用 upstream memory_resource 的 allocate 方法[5]。
其实是有一个默认的 memory_resource 的,就是默认的 ::operator new
和 ::operator delete
管理内存。
而且 memory_resource 必须要有 upstream,看 monotonic_buffer_resource
的源码:
monotonic_buffer_resource(memory_resource* __upstream) noexcept
__attribute__((__nonnull__))
: _M_upstream(__upstream)
{ _GLIBCXX_DEBUG_ASSERT(__upstream != nullptr); }
用法
std::pmr::monotonic_buffer_resource - cppreference.com
Cpp17/markdown/src/ch29.md at master · MeouSker77/Cpp17
总结
- stl 容器需要
allocator<T>
, allocator_traits
规定访问 allocator 成员的标准接口[6]。可以实现 rebind 等操作,以及对象的 construct 和 destroy 也在 traits 中有默认实现- 但是不同的 allocator 没法通用、无状态、难以实现自定义的分配策略,所以用 polymorphic_allocator 和 memory_resource 出现了
polymorphic_allocator<T>
内部持有 memory_resource 指针,统一了 allocator 接口,只封装了一层要管理的类型 T- memory_resource 内部来实现分配策略,和分配的具体对象无关
- 提供了五种特别的 memory_resource 实现