allocator、polymorphic allocator 与 memory_resource
Published on 2024-07-05 11:37 in 分类: C-CPP with dutrmp19
分类: C-CPP

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 只需要:

  1. value_type
  2. allocate
  3. deallocate

rebind 目的是实现 rebind(allocator<TypeA>, TypeB) == allocator<TypeB>
C++11 已经使用 allocator_traits 实现了这种想法,并且 C++17 就抛弃了以前把 rebind 放到 allocator 内部实现的方法。

还有两个函数 constructdestroy,如果提供,就会使用我们自定义的,不提供也没有问题,allocator_traits 提供了默认的实现,
container 本身就是通过 allocator_traits 来使用 construct 和 destroy。

  1. construct:在 allocate 后调用,在分配的内存上初始化对象
  2. 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 呢,因为

  1. allocator 是模板签名的一部分。不同 allocator 的容器,无法混用。
  2. c++11 以前,allocator 无状态;c++11 以后,可以有状态,然而 allocator 类型复杂难用。
  3. 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 的指针,分配策略通过 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.

——std::pmr::polymorphic_allocator - cppreference.com

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 提供了五种:

内存资源 行为
new_delete_resource() 返回一个调用newdelete的内存资源的指针
synchronized_pool_resource 创建更少碎片化的、线程安全的内存资源的类
unsynchronized_pool_resource 创建更少碎片化的、线程不安全的内存资源的类
monotonic_buffer_resource 创建从不释放、可以传递一个可选的缓冲区、线程不安全的类
null_memory_resource() 返回一个每次分配都会失败的内存资源的指针

这三种比较重要:

  1. std::pmr::monotonic_buffer_resource - cppreference.com
  2. std::pmr::synchronized_pool_resource - cppreference.com
  3. std::pmr::unsynchronized_pool_resource - cppreference.com

在接口调用中提到了 upstream 的概念:如果当前 memory_resource 内存不足,则调用 upstream memory_resource 的 allocate 方法
其实是有一个默认的 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

总结

  1. stl 容器需要 allocator<T>
  2. allocator_traits 规定访问 allocator 成员的标准接口。可以实现 rebind 等操作,以及对象的 construct 和 destroy 也在 traits 中有默认实现
  3. 但是不同的 allocator 没法通用、无状态、难以实现自定义的分配策略,所以用 polymorphic_allocator 和 memory_resource 出现了
  4. polymorphic_allocator<T> 内部持有 memory_resource 指针,统一了 allocator 接口,只封装了一层要管理的类型 T
  5. memory_resource 内部来实现分配策略,和分配的具体对象无关
  6. 提供了五种特别的 memory_resource 实现

  1. std::allocator_traits - cppreference.com ↩︎

  2. 游戏引擎开发新感觉!(6) c++17 内存管理 - 知乎 ↩︎

  3. std::pmr::memory_resource - cppreference.com ↩︎

  4. Cpp17/markdown/src/ch29.md at master · MeouSker77/Cpp17 ↩︎

  5. std::pmr::monotonic_buffer_resource - cppreference.com ↩︎

  6. std::allocator_traits - cppreference.com ↩︎

如果您有任何关于文章的建议,欢迎评论或在 GitHub 提 PR

作者:dutrmp19
本文为作者原创,转载请在 文章开头 注明出处:https://www.cnblogs.com/dutrmp19/p/18285521
遵循 CC 4.0 BY-SA 版权协议


posted @   dutrmp19  阅读(41)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
点击右上角即可分享
微信分享提示