allocator、polymorphic allocator 與 memory_resource
cpp allocator
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 前呼叫,在記憶體上析構分配的物件
以下是一個非常簡單的 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.
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 提供了五種[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 實現
std::allocator_traits - cppreference.com ↩︎
遊戲引擎開發新感覺!(6) c++17 記憶體管理 - 知乎 ↩︎
std::pmr::memory_resource - cppreference.com ↩︎
Cpp17/markdown/src/ch29.md at master · MeouSker77/Cpp17 ↩︎
std::pmr::monotonic_buffer_resource - cppreference.com ↩︎
std::allocator_traits - cppreference.com ↩︎