当前位置: 首页 > 工具软件 > mempool > 使用案例 >

ceph mempool

丁灿
2023-12-01

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中的内存大小

 类似资料:

相关阅读

相关文章

相关问答