C++11实现高效内存池(自我记录)

前言

本篇文章主要内容为讲述自己对于一位大佬写的C++11内存池项目的解析。初次上手项目,很多知识点都没有遇到过,有些知识点会提供其他的博文帮助理解有描述不清楚或存在错误的地方还请大家一一指出(解析均位于代码注释中)。


【源码剖析】MemoryPool —— 简单高效的内存池 allocator 实现

项目源码:GitHub源码

项目介绍

内存池是什么

话不多说,这里摘录最具权威的原作者对于项目的解释:
什么是内存池?什么是 C++ 的 allocator?
内存池简单说,是为了减少频繁使用 malloc/free new/delete 等系统调用而造成的性能损耗而设计的。当我们的程序需要频繁地申请和释放内存时,频繁地使用内存管理的系统调用可能会造成性能的瓶颈,嗯,是可能,毕竟操作系统的设计也不是盖的(麻麻说把话说太满会被打脸的(⊙v⊙))。内存池的思想是申请较大的一块内存(不够时继续申请),之后把内存管理放在应用层执行,减少系统调用的开销。

那么,allocator 呢?它默默的工作在 C++ 所有容器的内存分配上。默默贴几个链接吧:

C++ allocator

C++学习笔记(十) 内存机制与Allocator

class templatestd::allocator

allocator_traits

当你对 allocator 有基本的了解之后,再看这个项目应该会有恍然大悟的感觉,因为这个内存池是以一个 allocator 的标准来实现的。一开始不明白项目里很多函数的定义是为了什么,结果初步了解了 allocator 后才知道大部分是标准接口。这样一个 memory pool allocator 可以与大多数 STL 容器兼容,也可以应用于你自定义的类。像作者给出的例子 —— test.cpp, 是用一个基于自己写的 stack 来做 memory pool allocator 和 std::allocator 性能的对比 —— 最后当然是 memory pool allocator 更优

内存池是一个一个的 block 以链表的形式连接起来,每一个 block 是一块大的内存,当内存池的内存不足的时候,就会向操作系统申请新的
block 加入链表。还有一个 freeSlots_ 的链表,链表里面的每一项都是对象被释放后归还给内存池的空间,内存池刚创建时
freeSlots_> 是空的,之后随着用户创建对象,再将对象释放掉,这时候要把内存归还给内存池,怎么归还呢?就是把指向这个对象的内存的指针加到
freeSlots_ 链表的前面(前插)。
用户在创建对象的时候,先检查 freeSlots_ 是否为空,不为空的时候直接取出一项作为分配出的空间。否则就在当前 block 内取出一个 Slot_ 大小的内存分配出去,如果 block 里面的内存已经使用完了呢?就向操作系统申请一个新的 block。

内存池工作期间的内存只会增长,不释放给操作系统。直到内存池销毁的时候,才把所有的 block delete 掉

注释源码: 点我到注释源码
下图左一为刚申请的Blcok↓
C++11实现高效内存池(自我记录)

MemoryPool头文件

#ifndef MEMORY_POOL_H
#define MEMORY_POOL_H

#include <climits>
#include <cstddef>

//allocate    分配一个对象所需的内存空间
//
//deallocate   释放一个对象的内存(归还给内存池,不是给操作系统)
//
//construct   在已申请的内存空间上构造对象
//
//destroy  析构对象
//
//newElement  从内存池申请一个对象所需空间,并调用对象的构造函数
//
//deleteElement  析构对象,将内存空间归还给内存池
//
//allocateBlock  从操作系统申请一整块内存放入内存池


template <typename T, size_t BlockSize = 4096>
class MemoryPool
{
public:
    /* Member types */
    typedef T               value_type;//值类型
    typedef T*              pointer;//指针类型
    typedef T&              reference;//非常量引用
    typedef const T*        const_pointer;//常量指针
    typedef const T&        const_reference;//常量引用
    typedef size_t          size_type;//无符号整型 unsigned int
    typedef ptrdiff_t       difference_type;//普通int类型
    typedef std::false_type propagate_on_container_copy_assignment;
    typedef std::true_type  propagate_on_container_move_assignment;
    typedef std::true_type  propagate_on_container_swap;

    template <typename U> struct rebind {//结构体rebind
        typedef MemoryPool<U> other;//将 Memory<U> 改名为 other
    };

    /* Member functions */
    MemoryPool() noexcept;
    MemoryPool(const MemoryPool& memoryPool) noexcept;
    MemoryPool(MemoryPool&& memoryPool) noexcept;
    template <class U> MemoryPool(const MemoryPool<U>& memoryPool) noexcept;

    ~MemoryPool() noexcept;

    MemoryPool& operator=(const MemoryPool& memoryPool) = delete;//删除默认拷贝构造函数
    MemoryPool& operator=(MemoryPool&& memoryPool) noexcept;//移动赋值函数

    pointer address(reference x) const noexcept;//返回非常量引用类型变量地址
    const_pointer address(const_reference x) const noexcept;//返回常量引用类型变量地址

    // Can only allocate one object at a time. n and hint are ignored
    //一次只能为一个目标分配空间 常量指针(const T*  -> 不可以修改该地址存放的数据)
    pointer allocate(size_type n = 1, const_pointer hint = 0);//均为可缺省参数
    void deallocate(pointer p, size_type n = 1);//回收内存 归还给Block内存块

    size_type max_size() const noexcept;//计算最大可使用的 Slot 槽

    template <class U, class... Args> void construct(U* p, Args&&... args);
    template <class U> void destroy(U* p);//调用析构函数释放 p 所指空间

    template <class... Args> pointer newElement(Args&&... args);
    void deleteElement(pointer p);

private:
    union Slot_ {
        value_type element;//使用时为 value_type 类型
        Slot_* next;//需要回收时为 Slot_* 类型并加入 空闲链表中
    };

    typedef char* data_pointer_;//字符类型指针
    typedef Slot_ slot_type_;//槽类型
    typedef Slot_* slot_pointer_;//槽类型指针

    slot_pointer_ currentBlock_;//指向第一块 Block 内存块
    slot_pointer_ currentSlot_;//指向第一个可用元素 Slot  元素链表的头指针
    slot_pointer_ lastSlot_;//可存放元素的最后指针
    slot_pointer_ freeSlots_;//被释放的元素存放于空闲链表, freeSlots_为链表头指针

    size_type padPointer(data_pointer_ p, size_type align) const noexcept;
    void allocateBlock();//申请一块新的Block

    //静态断言:当申请的BlockSize小于两个slot槽的内存时报错
    static_assert(BlockSize >= 2 * sizeof(slot_type_), "BlockSize too small.");
};

#include "MemoryPool.tcc"

#endif // MEMORY_POOL_H

函数实现MemoryPool.tcc

#ifndef MEMORY_BLOCK_TCC
#define MEMORY_BLOCK_TCC


//计算对齐所需补的空间
template <typename T, size_t BlockSize>
inline typename MemoryPool<T, BlockSize>::size_type
MemoryPool<T, BlockSize>::padPointer(data_pointer_ p, size_type align)
const noexcept
{
  uintptr_t result = reinterpret_cast<uintptr_t>(p);//占有的空间
  return ((align - result) % align);//需要补的空间
}


//构造函数 初始化所有指针为 nullptr
template <typename T, size_t BlockSize>
MemoryPool<T, BlockSize>::MemoryPool()
noexcept
{
  currentBlock_ = nullptr;//指向第一块Block区 即Block内存块链表的头指针
  currentSlot_ = nullptr;//当前第一个可用槽的位置
  lastSlot_ = nullptr;//最后可用Slot的位置
  freeSlots_ = nullptr;//空闲链表头指针
}


//拷贝构造函数
template <typename T, size_t BlockSize>
MemoryPool<T, BlockSize>::MemoryPool(const MemoryPool& memoryPool)
noexcept :
MemoryPool()
{}


//移动构造函数
template <typename T, size_t BlockSize>
MemoryPool<T, BlockSize>::MemoryPool(MemoryPool&& memoryPool)
noexcept
{
  currentBlock_ = memoryPool.currentBlock_;
  memoryPool.currentBlock_ = nullptr;
  currentSlot_ = memoryPool.currentSlot_;
  lastSlot_ = memoryPool.lastSlot_;
  freeSlots_ = memoryPool.freeSlots;
}

//拷贝构造函数
template <typename T, size_t BlockSize>
template<class U>
MemoryPool<T, BlockSize>::MemoryPool(const MemoryPool<U>& memoryPool)
noexcept :
MemoryPool()
{}


//移动赋值函数
template <typename T, size_t BlockSize>
MemoryPool<T, BlockSize>&
MemoryPool<T, BlockSize>::operator=(MemoryPool&& memoryPool)
noexcept
{
  if (this != &memoryPool)//若不是自己给自己赋值,进入if主体
  {
    std::swap(currentBlock_, memoryPool.currentBlock_);//交换第一块Block的位置
    //swap函数内部使用的也是移动赋值机制,确保了不会有多个指针指向一块空间,这样就不会出现一块空间被重复释放的问题
    currentSlot_ = memoryPool.currentSlot_;//其余指针赋值
    lastSlot_ = memoryPool.lastSlot_;
    freeSlots_ = memoryPool.freeSlots;
  }
  return *this;
}



template <typename T, size_t BlockSize>
MemoryPool<T, BlockSize>::~MemoryPool()//析构函数
noexcept
{
  slot_pointer_ curr = currentBlock_;//指向第一块内存区block
  while (curr != nullptr) {
    slot_pointer_ prev = curr->next;//保存下一个槽的位置
    operator delete(reinterpret_cast<void*>(curr));//释放空间 转为 void* 不需要调用析构函数
    curr = prev;//往前走
  }
}


//返回引用类型变量地址
template <typename T, size_t BlockSize>
inline typename MemoryPool<T, BlockSize>::pointer
MemoryPool<T, BlockSize>::address(reference x)
const noexcept
{
  return &x;
}


//返回常量引用类型变量地址
template <typename T, size_t BlockSize>
inline typename MemoryPool<T, BlockSize>::const_pointer
MemoryPool<T, BlockSize>::address(const_reference x)
const noexcept
{
  return &x;
}


//申请一块新的分区block
template <typename T, size_t BlockSize>
void
MemoryPool<T, BlockSize>::allocateBlock()
{
  // Allocate space for the new block and store a pointer to the previous one
  //申请 BlockSize字节大小的一块空间并用char* 类型指针接收
  data_pointer_ newBlock = reinterpret_cast<data_pointer_>
                           (operator new(BlockSize));
  //newBlock成为新的block内存首址              
  reinterpret_cast<slot_pointer_>(newBlock)->next = currentBlock_;

  //currentBlock 更新为 newBlock的位置
  currentBlock_ = reinterpret_cast<slot_pointer_>(newBlock);

  // Pad block body to staisfy the alignment requirements for elements
  //保留最后一个slot
  data_pointer_ body = newBlock + sizeof(slot_pointer_);

  //求解空间对齐需要的字节数
  size_type bodyPadding = padPointer(body, alignof(slot_type_));

  //若出现残缺不可以作为一个slot的空间,跳过这块空间作为第一个可用slot的位置 
  currentSlot_ = reinterpret_cast<slot_pointer_>(body + bodyPadding);
  lastSlot_ = reinterpret_cast<slot_pointer_>
              (newBlock + BlockSize - sizeof(slot_type_) + 1);
   //始址: newBlock  块大小 BlockSize,末址 newBlock + BlockSize 末址减去一个slot槽大小
   //得到倒数第二个slot的末址 再加一得到最后一块slot的始址
}



template <typename T, size_t BlockSize>
inline typename MemoryPool<T, BlockSize>::pointer
MemoryPool<T, BlockSize>::allocate(size_type n, const_pointer hint)
{//在内存池中申请 n 个Node,hint 默认设置为 空
  if (freeSlots_ != nullptr) {//空闲链表不为空,表示链表中有slot可以使用
    pointer result = reinterpret_cast<pointer>(freeSlots_);//将头结点分配出去
    freeSlots_ = freeSlots_->next;//头结点更新
    return result;
  }
  else {//空闲链表没有 slot 
  //如果当前可用slot槽已经到了最后的位置,用完之后需要再创建一块Block方便后续操作
    if (currentSlot_ >= lastSlot_)
      allocateBlock();
    return reinterpret_cast<pointer>(currentSlot_++);//返回分配的空间并更新 currentSlot
    //currentSlot 指向Block内存块中最前面可用的Slot槽 (freeSlot链表中不算)
  }
}



template <typename T, size_t BlockSize>
inline void
MemoryPool<T, BlockSize>::deallocate(pointer p, size_type n)
{
  if (p != nullptr) {
    reinterpret_cast<slot_pointer_>(p)->next = freeSlots_;//union指针域被赋值 之前的数据销毁
    freeSlots_ = reinterpret_cast<slot_pointer_>(p);//空闲链表头结点更新
  }
}


//计算最大可用槽Slot的个数
template <typename T, size_t BlockSize>
inline typename MemoryPool<T, BlockSize>::size_type
MemoryPool<T, BlockSize>::max_size()
const noexcept
{ //无符号整型 unsiged int 类型运算,先将 -1 转换为无符号整型 即无符号整型最大值
  size_type maxBlocks = -1 / BlockSize;//无符号类型数值可以表示的Block的个数
  //unsigned int MAX / BlockSize 向下取整

  return (BlockSize - sizeof(data_pointer_)) / sizeof(slot_type_) * maxBlocks;
     //  (BlockSize - sizeof(data_pointer)) / sizeof(slot_type_) →指每一块Block中可以存放的slot槽个数
    // 最大可以有 maxBlocks 块内存块
    //因此最大的可用槽个数 为每一块Block可以使用的Slot槽个数乘以最大可用Block
}



template <typename T, size_t BlockSize>
template <class U, class... Args>
inline void
MemoryPool<T, BlockSize>::construct(U* p, Args&&... args)
{
//完美转发 保存参数原有的属性 是左值还是左值 是右值还是右值
  new (p) U (std::forward<Args>(args)...);
  //placement new 的应用,即在已经申请的空间上申请内存分配
}


//调用析构函数
template <typename T, size_t BlockSize>
template <class U>
inline void
MemoryPool<T, BlockSize>::destroy(U* p)
{//调用析构函数释放这一块内存
  p->~U();
}



template <typename T, size_t BlockSize>
template <class... Args>
inline typename MemoryPool<T, BlockSize>::pointer
MemoryPool<T, BlockSize>::newElement(Args&&... args)
{
//为每个元素分配空间
  pointer result = allocate();
  construct<value_type>(result, std::forward<Args>(args)...);//参数包解包过程
  return result;//返回每一个新申请空间的地址
}


//删除元素
template <typename T, size_t BlockSize>
inline void
MemoryPool<T, BlockSize>::deleteElement(pointer p)
{//                                       ↑传入 T* 类型的指针
  if (p != nullptr) {//判断指针为非空
    p->~value_type();//调用值类型的析构函数  typedef T  value_type;
    deallocate(p);//将这块内存还给内存池
  }
}

#endif // MEMORY_BLOCK_TCC
上一篇:redis集群模式-key分配一个slot


下一篇:redis cluster 的核心原理分析:gossip 通信、jedis smart 定位、主备切换