C++11实现高效内存池

C++11内存池设计与实现深度解析 这里是因为直接调用默认拷贝构造会出现问题,因此重写拷贝构造的接口,实际上用的是委托的默认构造函数,这样即使不小心调用了拷贝构造,最后实际上执行的仍是默认构造,从而避免出现因浅拷贝而出现的问题。这里借助通俗的模型帮助大家理解内存池是怎么运作的。假设我们有一个快递站(内存池),里面有很多货架(Block),每个货架有很多格子(Slot)用于存放快递(要存储的对象),这里假设所有快递占用的空间一样。注意:一个内存池有很多个内存块(block),一个内存块有多个内存槽(slot),内存槽一般就可以用来存储对象。 阅读详情

前言

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


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

项目源码:GitHub源码

项目介绍

内存池是什么

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

allocator详解

那么,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↓
Blcok详情


MemoryPool.tcc

该文件对Memory.h文件中声明过的函数进行实现,在此一些简单的类函数就不再一一赘述,只挑选出部分函数给出详细剖析,其余可在最后的代码汇总中查看注释。函数中使用了大量的强制转换,这里贴几个有关博客:
【C++】四种强制类型转换
c++类型转换


allocateBlock 创建Block块

函数用于申请一块Block内存块,并使用头插法放入Blcok内存块链表。

//申请一块新的分区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 用于Block链表的链接
  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的始址
}

这里需要注意的是:在生成一块BlockSize字节大小的Block内存块时需要保留Slot类型大小的空间用于后续Block链表的链接。对应图中的蓝色内存区。此外,在调用padPointer函数 的时候传入的参数使用了 C++11 的新特性 C++11 内存对齐 alignof alignas


padPointer 空间对齐

每一个Block内存块的大小(BlockSize字节)并不是刚好为Slot槽大小的整数倍(取决于使用者定义的BlockSize),例如一个内存块Block大小设置为BlockSize = 11 bytes,每个Slot槽存放数据的大小为4字节时,BlockSize % sizeof(Slot) = 11 % 4 = 2 … 3,余下的三个字节是不足一个Slot槽所需空间的,这时我们必须略去这些多余的空间。上述空间在图中用橙色区域进行了表示。
在这里插入图片描述

//计算对齐所需补的空间
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);//将char* 类型的指针转换为 uintptr_t 类型(无符号整型)
  return ((align - result) % align);//多余不够一个Slot槽大小的空间,需要跳过
}

MemoryPool 构造函数

这里说明一下内存池中使用到的指针,先看看原作者是怎么描述的:

    slot_pointer_ currentBlock_;  // 内存块链表的头指针
    slot_pointer_ currentSlot_;   // 元素链表的头指针
    slot_pointer_ lastSlot_;      // 可存放元素的最后指针
    slot_pointer_ freeSlots_;     // 元素构造后释放掉的内存链表头指针

当时阅读项目时很不解的点就是 currentSlot 指针的含义,作者给出的解释是元素链表的头指针 ,我思来想去,每一个Slot槽是一个联合体:

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

Slot槽
当Slot用来存放数据时,根据联合的特性,另一个指针属性是不可以使用的,这两个属性的关系也很简单,不可同时出现。因此当Slot槽存储数据时不可能同时是一个链表(链表至少需要一个指针),正是这样,再根据后面对于currentSlot指针的使用我将其解释为
指向第一个可用元素 Slot ,这是狭义上的第一个可用,在后面allocate函数中为每一个元素分配空间的时候currentSlot指针是明确的只能往后走的,也就是说这个 “可用” 不包括已经在空闲链表上的Slot(使用过但现在释放并还给内存池的槽),指向的是从没有被置放过数据的Slot槽。

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

allocate 内存分配

为每个element元素分配内存,先查询槽的空闲链表是否为空,若不为空则直接将链表头结点表示的空间分配出去,反之到Block中分割新的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链表中不算)
  }
}

当 currentSlot 到了 lastSlot 的位置表明该Block即将用完,必须另起炉灶,再向操作系统申请新的Block内存块。这里有一个疑点

  if (currentSlot_ >= lastSlot_)
   	     allocateBlock();

也就是说在 currentSlot 指针等于 lastSlot 的时候调用allocateBlock函数,而在此时这块空间还没有被分配出去,调用函数allocateBlock时我们注意到在函数中currentSlot指针被修改指向了新的内存块Block中的空间了!!!
currentSlot_ = reinterpret_cast<slot_pointer_>(body + bodyPadding);
也就是说之前的内存块中最后一个Slot槽大小的空间还未被分配出去就已经改变了currentSlot指针,于是我将if条件修改为了

  if (currentSlot_ > lastSlot_)
      allocateBlock();

再次运行时就报错了,,,在这里插入图片描述


deallocate 释放内存

这里的释放内存并不是将这块空间真正的释放掉了(归还给操作系统),而是归还给内存池。这样使得内存池的空间可复用性更高,不需要频繁机械地向OS申请内存,那么这里又是如何将释放掉的Slot槽回收起来呢?考虑到Slot槽并不一定是连续释放的,因此使用链表的形式对这些离散分布的内存槽进行连接,既然是链表,那么必须有指针来完成链表的链接工作,这个指针就大有来头了,回顾一下 Slot 槽的内部结构:

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

既然使用了联合,那么很显然就是用来节省内存的,说到这里可以猜测到槽中的 Slot* 属性是用来充当链表中的指针,当我们创建对象的时候使用 value_type 也就是普通值类型,而销毁数据之后就可以使用它的另一属性将其放入空闲链表中。下图为一块Block块空闲链表的链接情况:
空闲链表
deallocate具体操作:

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的地址


max_size 计算最大可用槽的个数

先放上代码:

//计算最大可用槽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
}

这个函数的作用就是通过计算确定最大可用的Slot的个数,这样可以防止越界导致数据损坏(越界之后的地址由于是循环计数机制会回到起点,对已经存储数据的空间进行破坏),这里的计算方式很有意思,我们一起看看吧

 size_type maxBlocks = -1 / BlockSize;

这里的 size_type 是被 typedef 过的 unsigned int, BlockSize为 size_t (同无符号整型), 注意这个分数的上部,是不是很有意思,用一个负数去进行运算,最后赋值给无符号的数值类型,先别凌乱,慢慢来分析:
首先计算机内部为了能够更加轻松自然地去进行负数运算,选择原码是万万不可的,打个比方:
32位系统中原码的表示为 最高位为符号位,其余31位为数值位。
若使用原码进行计算:
在这里插入图片描述
2 + (-1)竟然等于 -3!!!很明显这不可行。最终科学家们找到一种很实用的方式就是补码运算
使用补码的好处
负数的补码等于其反码加1。
在这里插入图片描述
求得-1的补码其实就是 unsigned int 类型可以表示的最大值,那么 maxBlock 表示的就是内存池最多能生成的内存块Block的数量。而返回的是一个表达式:
return (BlockSize - sizeof(data_pointer_)) / sizeof(slot_type_) * maxBlocks;
BlockSize - sizeof(data_pointer) 表示每一个Block块除去一个指针大小之后的可用空间,“ (BlockSize - sizeof(data_pointer_)) / sizeof(slot_type_) ”表示一个Block块真正可以使用来存储数据的Slot槽的个数,每一块Block可以用的槽个数再乘上内存块的最大个数即可得到一个内存池可以使用的Slot槽的个数。
后续还有一些基本的类函数就不再一一介绍了,代码中含有注释。

~MemoryPool 析构函数

这里很有操作的是将 Slot* 类型指针转换为 void* 类型再对其释放空间,原解释是 void*类型释放不需要析构函数;

如果void 指向一个class类,那么系统由于认为void 指向一个普通的内存空间,所以释放指针时系统class的析构函数不会调用
转自 释放void*指针

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

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);//多余不够一个Slot槽大小的空间,需要跳过
}

//构造函数 初始化所有指针为 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 用于Block链表的链接
  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

写在最后

笔者科班大二在学,初次接触项目,有很多还未完全理解透的地方,写这篇博客主要是记录成长的过程以及分享自己在反复揣摩源代码并查找大量资料最后的理解,存在表述不清或有误的地方还请指出,笔者将尽一切力量去修改纠正。文笔水平较低,各位大哥轻喷点哈哈哈哈(手动狗头)~~~

C++内存池对齐计算:8种高性能场景下的核心策略与实践 内存对齐是计算机系统中提升数据访问效率的基础原理,它要求数据在内存中的起始地址是特定值的整数倍,以便CPU能以最少的时钟周期完成读取。这一机制通过减少内存总线访问次数,直接提升了程序性能。在C++高性能服务开发中,内存池作为管理内存分配的核心组件,其对齐计算的精确性直接决定了系统的吞吐量和延迟表现。从避免缓存行伪共享、优化SIMD向量化计算,到满足硬件DMA的苛刻要求,正确的对齐策略是构建游戏服务器、金融交易系统等低延迟应用的技术基石。本文深入探讨了固定大小对象池、变长内存块、缓存行对齐等8种经典场景下的对 阅读详情

相关推荐

MemoryPool:使用 C++11 的简单内存池实现

内存池 使用 C++11 的简单内存池实现。 与 Visual Studio 2015、g++4.8 和 clang++3.4 兼容。 一个使用和文档的例子即将到来。

【项目】C++实现高并发内存池

高并发内存池项目的实现

m0_64645696的博客 3595

MemPool:仅标头的C ++ 17库,实现简单的并发无锁内存池

免责声明:此代码是一个操场,未经全面测试,很可能包含一些令人讨厌的错误。 请勿在生产中使用它! 目录 记忆池 此仅标头的C ++ 17库提供了一种简单的一致的无锁的单个类型元素的内存池的实现。 元素类型作为第一个模板参数传递。 细节 界面 template < typename xss=removed xss=removed> > class MemoryPool final { public: // construction MemoryPool (); MemoryPool (Allocator& alloc); explicit MemoryPool ( size_t initialCapaci

C++11/14之内存池

内存池的概念和实现原理概述 malloc:内存浪费,频繁分配小块内存,则浪费更加显得明显 “内存池”,要解决什么问题? (1)减少malloc的次数,减少malloc()调用次数就意味着减少对内存的浪费 (2)减少malloc的调用次数,是否能够提高程序运行效率? 会有一些速度和效率上的提升,但是提升不明显; “内存池的实现原理”: 用malloc申请一大块内存,当你要分配的时候,我从这一大块内...

baidu_41388533的博客 1216

从PHY芯片看工业网络精准时钟:IEEE 1588v2(PTP)协议实现与选型指南

本文深入解析了工业网络中IEEE 1588v2(PTP)协议的实现与PHY芯片选型要点。通过对比KG7101、RTL8211F等芯片的关键参数,揭示硬件时间戳对纳秒级时钟同步的决定性作用,并提供PTP网络设计中的拓扑布局与配置优化技巧,助力工业自动化场景实现精准时间同步。

weixin_42628688的博客 500

CVE-2017-7921漏洞深度解析:海康设备明文密码泄露原理与七层防御

网络摄像头安全本质上是嵌入式Web服务安全,其核心在于身份认证机制是否健全。CVE-2017-7921并非传统远程执行漏洞,而是因信任错位导致的未授权API暴露——`/Security/users/1`接口绕过Session校验,直接返回admin明文密码。该问题根植于安防设备早期‘物理隔离+前端可控’的设计假设,在P2P、云平台等新场景下全面失效。技术价值在于揭示了轻量级HTTP服务中路径级访问控制缺失的普遍风险;典型应用场景包括ONVIF协议接管、Telnet后门登录及NVR侧横向移动。本文基于DS-2

weixin_33324197的博客 399

C++内存池的内存对齐问题

摘要 内存对齐是硬件要求、软件正确性和性能优化的三重刚需。内存池必须实现严格对齐,否则会导致性能暴跌、内存碎片甚至程序崩溃。C++内存池主要采用两种对齐方式:手动位运算(极致性能)和C++11标准关键字(优雅安全)。生产级内存池的SizeClass策略遵循「分级对齐+2的幂次取整」原则,小内存精细对齐(8/16字节),大内存粗粒度对齐(64/128字节),在内存利用率和管理效率间取得平衡。这种设计被tcmalloc/jemalloc等工业级内存池广泛采用,是保证高性能和稳定性的核心机制。

m0_62807361的博客 882

稳压二极管选型指南:关键参数与电路稳定性优化

本文深入解析了稳压二极管在电路设计中的核心作用与选型要点。文章详细阐述了稳定电压、动态电阻、额定功耗等关键参数对电路稳定性的影响,并结合实战案例,提供了从需求分析到参数验证的系统化选型流程与优化策略,帮助工程师避免常见设计陷阱,实现电路稳定性优化。

weixin_29196891的博客 680

Teemo 轻量级高性能 C++ 工具库:开箱即用的后端开发开源组件指南

Teemo:轻量级C++跨平台基础工具库 Teemo是一款专为C++开发设计的开源基础工具库,解决项目中重复编写底层工具(如日志、编解码、内存管理等)的痛点。其核心优势包括: 零依赖:纯C++11实现,跨平台支持Linux/Windows/macOS及嵌入式设备。 高性能:内置内存池、无锁队列、零拷贝优化,提升并发性能。 模块化:按需编译,提供日志、编解码、定时器、文件操作等7大模块,覆盖90%底层需求。 易用性:接口简洁统一,支持头文件或静态库接入,MIT协议可商用。 典型场景:后端服务、嵌入式设备、游戏

踏板鞋的专栏 379

小红书图文AI创作:输入主题,一键生成图文(coze工作流教程+获取)

输入主题,工作流一键生成小红书风格图文,30s一条,可批量生成

2301_79384090的博客 2798

【eclipse】win 10:eclipse 4.9 下载、安装和汉化(支持中英切换)

目录 一、eclipse下载 二、eclipse安装 三、eclipse在线汉化 四、eclipse离线汉化(推荐) 一、eclipse下载 ①官网下载地址(收费):https://www.eclipse.org/downloads/点击前往下载 ②【推荐】我的百度网盘下载地址(包括离线汉化包):点击下载 提取码:0nfw ③eclipse4.9其他地址下载:http...

顾三殇 —— 博客空间(软件测试) 1万+

如何用C++打造高性能工业内核,并用Python实现快速功能迭代?

掌握工业软件高效开发之道,通过C++打造高性能内核,结合Python实现灵活扩展。本文详解工业软件的模块化开发(C++ 核心 + Python 脚本扩展)在智能制造、自动化等场景的应用,兼顾性能与迭代速度,值得收藏。

IterLoom的博客 1023

土壤数据库辅助工具SPAW计算土壤导水率

首先点击打开根据之前的1比100土壤数据查表得到各个组分含量其中Field Capacity是田间持水量Matric Bulk Density是基质粒密度。

所有专家都曾始于新手 3443

利用Proto3与懒汉单例实现高性能配置中心(C++实战解析)

本文详细解析了如何利用Proto3与懒汉单例模式在C++中实现高性能配置中心。通过Proto3的二进制编码和跨语言支持,结合线程安全的懒汉单例实现,显著提升配置管理的效率和性能,适用于电商秒杀等高频场景。文章包含实战代码示例和性能优化技巧,帮助开发者构建可靠的系统参数配置模块。

elastic6hunter的博客 793

IGBT基础知识及应用电路仿真(Multisim)

本文系统介绍了IGBT(绝缘栅双极晶体管)及其应用电路。首先详细解析了IGBT的基本结构、工作原理和特性参数,重点阐述了其五层半导体结构、电导调制效应和电压驱动特性。随后通过Multisim仿真展示了IGBT在四种典型功率变换电路中的应用:半桥驱动电路实现隔离式互补驱动;全桥逆变电路完成DC-AC转换;三相桥式逆变电路生成三相交流电;直流斩波电路(BUCK/BOOST/BUCK-BOOST)实现电压升降变换。文章通过理论分析与仿真波形相结合,全面呈现了IGBT在中高压大功率场景下的优异性能和灵活应用。

XU157303764的博客 1218

SAP UI5 主题设置全解析:从启动参数到运行时切换、跨域白名单与事件监听

SAP UI5主题体系深度解析:从配置到切换的完整指南 摘要:SAP UI5主题机制远不止CSS换肤,其工程化实践涵盖启动配置、运行时API切换、跨库路径映射及安全管控等关键环节。通过bootstrap标签或URL参数设置初始主题,运行时可用Theming.setTheme无刷新切换。自定义主题需配置theme-roots路径,跨域加载需声明sap-allowed-theme-origins白名单。主题切换完成后会触发applied事件,适合执行视觉同步操作。该体系支持多品牌适配、集中式主题管理,在Fior

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 326

InstructPix2Pix在C++环境中的高性能实现

本文介绍了如何在星图GPU平台上自动化部署🪄 AI 魔法修图师 - InstructPix2Pix镜像,实现高性能的图像编辑功能。该镜像能够根据自然语言指令智能修改图片内容,例如调整亮度、对比度或风格转换,显著提升图像批量处理效率,适用于内容创作和媒体制作等场景。

weixin_35364187的博客 217

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

终极指南:如何为moodycamel::ConcurrentQueue定制高效内存分配器

在高性能并发编程中,内存分配策略直接影响系统吞吐量和延迟表现。moodycamel::ConcurrentQueue作为C++11环境下的多生产者多消费者无锁队列,其默认内存分配器虽然稳定可靠,但在特定场景下通过定制内存分配策略可以进一步释放性能潜力。本文将系统讲解从标准malloc到jemalloc的内存分配器定制全过程,帮助开发者为ConcurrentQueue打造专属内存管理方案。 ##

gitblog_00659的博客 497

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

如何用C++加速Python多模态模型API?工业级部署核心机密曝光

掌握多模态大模型的本地部署与 API 开发(Python+C++),解决高并发、低延迟工业级应用难题。结合C++性能优势与Python生态灵活性,实现模型加速与高效接口封装,适用于图像生成、语音理解等场景。核心方法实用,部署稳定高效,值得收藏。

CodeWhim的博客 1006

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

ESP32的现代C++进化论:解锁ESP-IDF中被忽视的11/14/17特性

本文深入探讨了如何在ESP32开发中充分利用现代C++(C++11/14/17)特性,如auto、constexpr、lambda等,以提升嵌入式系统的开发效率和性能。通过ESP-IDF框架的支持,开发者可以在资源受限的环境中实现面向对象编程的高级应用,包括内存优化设计模式和编译时多态。

nft7creator的博客 495

为什么顶尖AI系统都在用零拷贝?C++与Python协同设计的4个秘密

揭秘顶尖AI系统性能秘诀:实现C++与Python的多模态数据零拷贝交互,提升数据传输效率。适用于高并发、低延迟场景,通过内存共享、智能指针管理、缓冲区复用和接口抽象四大设计,显著降低系统开销。值得收藏的技术实践解析,点击了解实现细节。

QuickProceed的博客 993

C++调用YOLO ONNX模型实现高效视频目标检测:从模型加载到实时推理优化

整套代码我已经压成一个最小工程,CMake 一句就能出可执行文件。如果你也想亲手把“语音”换成“视频”,或者给数字人再加一双眼睛,可以顺手玩一下这个实验——从0打造个人豆包实时通话AI。我跟着做了一遍,发现它把 ASR→LLM→TTS 的整条链路拆成可插拔的积木,连 CMake 都写好了,小白也能 30 分钟跑通。下次打算把本文的 YOLO 结果用 WebRTC 推过去,让 AI 边“看”边“聊”,想想还挺带感。祝你编译不报错,推理不卡顿,我们下篇再见!

2600_94959834的博客 677

别再只用JSON了!用Protobuf 3.21.11给C++项目瘦身提速(附完整CMake配置)

本文详细介绍了如何在C++项目中使用Protobuf 3.21.11替代JSON进行高效序列化,显著提升性能并减少资源消耗。通过对比测试数据、CMake配置示例和高级优化技巧,帮助开发者实现项目瘦身和提速,特别适合数据密集型应用场景。

weixin_28718769的博客 172

AI股票分析师C++高性能扩展开发

本文介绍了如何在星图GPU平台上自动化部署 📈 AI 股票分析师daily_stock_analysis 镜像,以构建高性能的金融分析应用。该镜像通过集成C++高性能计算模块,能够显著提升多只股票批量技术指标分析与实时监控的处理速度,适用于个人投资者进行高效、自动化的股票组合分析与决策支持。

weixin_42608299的博客 88

现代C++高效内存管理:从RAII到自定义分配器的实战指南

内存管理是计算机程序设计的核心基础概念,它决定了程序如何高效、安全地使用系统内存资源。其基本原理涉及内存的分配、使用和释放,旨在优化性能并防止资源泄漏。在C++等系统级编程语言中,内存管理的技术价值尤为突出,直接关联到程序的性能、稳定性和资源效率。现代C++通过RAII(资源获取即初始化)范式、智能指针和移动语义等特性,在保证类型安全的同时,为精细化的内存控制提供了强大工具。这些技术在高性能计算、游戏引擎、嵌入式系统等对性能有严苛要求的应用场景中至关重要。本文聚焦于**内存池**的实现与**自定义分配器**

weixin_30314793的博客 354

一个高效的内存池实现

摘要: 在高效C++编程中看到一个不错的内存池实现方案,这里共享下,大家看看有什么不足。阅读全文Richard Wei 2012-05-05 23:23 发表评论 转载于:https://www.cnblogs.com/weiym/archive/2012/05/05/2653041.html...

weixin_34414650的博客 1249
上一篇: floyd算法求解地铁路线问题
下一篇: 贪心算法之装箱问题
nepu_bin
博客等级 码龄6年 513粉丝 113原创
评论 24
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nepu_bin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值