ceph mempool
与广义上的内存池不同,ceph自己实现的内存池并不用于管理内存,而是用于追踪内存
看一下源码中的注释描述翻译
/*******/
内存池
内存池是一种计算内存消耗的方法 一套容器。
内存池是静态声明的,详见pool_index_t。
每个内存池跟踪其包含的字节数和项数。
可以声明分配器并将其与类型关联,以便独立于池总数来跟踪它们。这项附加计数是可选的,只有在运行时启用调试时才会产生开销。这允许开发人员查看哪些类型正在消耗池资源。
使用内存池非常容易。
要创建一个新的内存池,只需在内存池列表(“DEFINE_MEMORY_POOLS_HELPER”中定义的内存池)中添加一个新名称即可。
对于创建的每个内存池,C++命名空间会自动创建一个包含一系列预定义stl容器的命名空间并有着对应的分配器
因此,对于mempool“osd”,我们有自动提供的:
mempool::osd::map
mempool::osd::multimap
mempool::osd::set
mempool::osd::multiset
mempool::osd::list
mempool::osd::vector
mempool::osd::unordered_map
将对象放入内存池
为了使用具有特定类型的内存池,需要一些额外的声明。
对于一个类:
struct Foo {
MEMPOOL_CLASS_HELPERS();
…
};
然后,在对应的.cc文件中,
MEMPOOL_DEFINE_OBJECT_FACTORY(Foo, Foo, osd);
第二个参数通常可以与第一个参数相同,除了当类包含嵌套范围时。比如说对于BlueStore::Onode,我们需要如此定义
MEMPOOL_DEFINE_OBJECT_FACTORY(BlueStore::Onode, bluestore_onode, bluestore_meta); 这是因为我们需要命名一些静态变量并且无法在变量名中使用::
注意:新的操作将会将定义在 MEMPOOL_DEFINE_OBJECT_FACTORY 中的对象大小硬编码到程序中,因此, 你不能不定义一个 helper/factory 给子类而直接将mempool注册到基类中,因为基类通常来说比子类空间要小。
为了使用STL容器,只需使用名称空间变量的容器类型。例如
mempool::osd::mapmyvec;
查询
查询进程内存的最简单方法是使用
Formater *f = …
mempool::dump(f);
这将打印有关所有内存池的信息。调试模式启用时,dump的运行时间复杂度为O(num_shards * num_type) 禁用调试名称时,它是O(num_shards)
您还可以使用如下代码查询指定mempool
size_t bytes=mempool::unittest_2::allocated_bytes();
size_t items=mempool::unittest_2::allocated_items();
时间复杂度为O(num_shard)。
注意您无法轻易查询每种类型,主要是因为调试模式是可选的,您不应该依赖该信息是一直可用的
/*******/
针对注释提到的点更细节的看一下,先从使用方法那一层关注,根据注释中的使用说明一栏,使用mempool时需要在该类中额外声明 MEMPOOL_CLASS_HELPERS() 和 MEMPOOL_DEFINE_OBJECT_FACTORY 看一下这两个个定义的实现
#define MEMPOOL_DEFINE_FACTORY(obj, factoryname, pool) \
namespace mempool { \
namespace pool { \
pool_allocator<obj> alloc_##factoryname = {true}; \
} \
}
// Use this for each class that belongs to a mempool. For example,
//
// class T {
// MEMPOOL_CLASS_HELPERS();
// ...
// };
//
#define MEMPOOL_CLASS_HELPERS() \
void *operator new(size_t size); \
void *operator new[](size_t size) noexcept { \
assert(0 == "no array new"); \
return nullptr; } \
void operator delete(void *); \
void operator delete[](void *) { assert(0 == "no array delete"); }
// Use this in some particular .cc file to match each class with a
// MEMPOOL_CLASS_HELPERS().
#define MEMPOOL_DEFINE_OBJECT_FACTORY(obj,factoryname,pool) \
MEMPOOL_DEFINE_FACTORY(obj, factoryname, pool) \
void *obj::operator new(size_t size) { \
return mempool::pool::alloc_##factoryname.allocate(1); \
} \
void obj::operator delete(void *p) { \
return mempool::pool::alloc_##factoryname.deallocate((obj*)p, 1); \
}
可以根据注释中发现,用 MEMPOOL_CLASS_HELPERS 来重载内存分配的 new 和 delete 函数,然后在对应的C++文件中定义 MEMPOOL_DEFINE_OBJECT_FACTORY 实现新的 new 和 delete,以 new 举例,new 函数调用的是在 MEMPOOL_DEFINE_FACTORY 中实现的 alloc_##factoryname.allocate(1) ,所以观察一下 pool_allocator 这个类的实现
template<pool_index_t pool_ix, typename T>
class pool_allocator {
pool_t *pool;
type_t *type = nullptr;
public:
typedef pool_allocator<pool_ix, T> allocator_type;
typedef T value_type;
typedef value_type *pointer;
typedef const value_type * const_pointer;
typedef value_type& reference;
typedef const value_type& const_reference;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
template<typename U> struct rebind {
typedef pool_allocator<pool_ix,U> other;
};
void init(bool force_register) {
pool = &get_pool(pool_ix);
if (debug_mode || force_register) {
type = pool->get_type(typeid(T), sizeof(T));
}
}
pool_allocator(bool force_register=false) {
init(force_register);
}
template<typename U>
pool_allocator(const pool_allocator<pool_ix,U>&) {
init(false);
}
T* allocate(size_t n, void *p = nullptr) {
size_t total = sizeof(T) * n;
shard_t *shard = pool->pick_a_shard();
shard->bytes += total;
shard->items += n;
if (type) {
type->items += n;
}
T* r = reinterpret_cast<T*>(new char[total]);
return r;
}
void deallocate(T* p, size_t n) {
size_t total = sizeof(T) * n;
shard_t *shard = pool->pick_a_shard();
shard->bytes -= total;
shard->items -= n;
if (type) {
type->items -= n;
}
delete[] reinterpret_cast<char*>(p);
}
T* allocate_aligned(size_t n, size_t align, void *p = nullptr) {
size_t total = sizeof(T) * n;
shard_t *shard = pool->pick_a_shard();
shard->bytes += total;
shard->items += n;
if (type) {
type->items += n;
}
char *ptr;
int rc = ::posix_memalign((void**)(void*)&ptr, align, total);
if (rc)
throw std::bad_alloc();
T* r = reinterpret_cast<T*>(ptr);
return r;
}
void deallocate_aligned(T* p, size_t n) {
size_t total = sizeof(T) * n;
shard_t *shard = pool->pick_a_shard();
shard->bytes -= total;
shard->items -= n;
if (type) {
type->items -= n;
}
::free(p);
}
void destroy(T* p) {
p->~T();
}
template<class U>
void destroy(U *p) {
p->~U();
}
void construct(T* p, const T& val) {
::new ((void *)p) T(val);
}
template<class U, class... Args> void construct(U* p,Args&&... args) {
::new((void *)p) U(std::forward<Args>(args)...);
}
bool operator==(const pool_allocator&) const { return true; }
bool operator!=(const pool_allocator&) const { return false; }
};
可以看到,调用时调用的allocate(1)就是分配一个的大小回去,并把这部分大小注册到对应的pool的shard中。相当于分配的内存实际上都是由allocate和deallocate分配和释放的,并且根据其隶属的shard增减shard中的内存大小