C++自定義簡單的內存池

內存池簡述

在C++的STL的容器中的容器如vector、deque等用的默認分配器(allocator)都是從直接從系統的堆中申請內存,用一點申請一點,效率極低。這就是設計內存池的意義,所謂內存池,就是一次性向系統申請一大片內存(預分配內存),后續誰要用內存,就從這個內存池中獲取一部分內存(Slot空間槽),回收也是換回內存池中,這樣就不用頻繁地直接和系統交互,提高效率;

數據結構

整體結構

+-----------------------------------------------------------------+
|     Header        |                    Body                     | 
+-------------------+-------------------+-----+-------------------+
| 鏈表指針(next)     | 填充(padding)     | Slot1 | Slot2 | ... | SlotN |
+-------------------+-------------------+-----+-------------------+
^                   ^                   ^
|                   |                   |
blockHeader         bodyStart           currentSlot_

釋放的Slot通過freeSlot來管理:
freeSlot的結構

Slot3<-Slot2<-Slot1^|
freeSlot

描述:

  1. 一個Block分為Header部分和Body部分,Header部分存著指向下一個Block的地址,Body部分存著許多Slot;
  2. Slot之間是沒有直接關系的,當有數據(不空)的時候,Slot存的就是數據,當Slot被釋放之后交給freeSlot來管理,此時釋放后的Slot存的是指向下一個釋放后的Slot的地址;

Slot的數據結構,初始的時候存數據,被釋放之后存下一個Slot的地址:
Slot的結構

    union Slot_{value_type element; // 存儲數據時使用Slot_ *next;        // 空閑時作為鏈表指針};

代碼解讀

main.cpp

#include <iostream>
#include <stack>
#include <vector>
#include <chrono> //高精度計時庫
#include "MemoryPool.h"using namespace std;// using是C++的重命名,類似與C的typedef,using更安全
// MemoryPool是分配器,分配器放到vector順序容器中
//  template <typename T>
//  using PoolStackContainer = vector<T, MemoryPool<T>>;// 再把PoolStackContainer作為stack的底層容器
//  template <typename T>
//  using PoolStack = stack<T, PoolStackContainer<T>>;// 上面的合起來寫
template <typename T>
using Stack = std::stack<T, vector<T>>; //為了單一變量,Stack和PoolStack的底層容器都設置成vector(std::stack默認的底層容器是deque)
template <typename T>
using PoolStack = std::stack<T, std::vector<T, MemoryPool<T>>>;
//using PoolStack = std::stack<T, std::deque<T, MemoryPool<T>>>;int main(){// 測試內存池分配/釋放MemoryPool<int> pool;//新建一個內存池int *p1 = pool.allocate(); //這里一個p就是一個slotpool.construct(p1, 42); // 構造對象std::cout << "*p1 = " << *p1 << std::endl;pool.destroy(p1);    // 銷毀對象pool.deallocate(p1); // 釋放內存// 性能對比:內存池 vs std::allocatorconst int N = 1000000;auto start_time1 = std::chrono::high_resolution_clock::now();//stdStack<int> std_stack;Stack<int> std_stack;for (int i = 0; i < N; ++i)std_stack.push(i); // 使用std::allocatorauto end_time1 = std::chrono::high_resolution_clock::now(); //高精度計時std::chrono::duration<double> time1 = end_time1 - start_time1;auto start_time2 = std::chrono::high_resolution_clock::now();PoolStack<int> pool_stack;for (int i = 0; i < N; ++i)pool_stack.push(i);auto end_time2 = std::chrono::high_resolution_clock::now();std::chrono::duration<double> time2 = end_time2 - start_time2;std::cout << "Test finished." << std::endl;std::cout << "棧用時: " << time1.count() << std::endl;std::cout << "內存池棧用時: " << time2.count() << std::endl;for (int i = 0; i < 1000;i++){//這個代碼輸出第1000個數字,判斷二者的里面的值是否相同,進而判斷是否成功插入std_stack.pop();pool_stack.pop();}cout << std_stack.top() << endl;cout << pool_stack.top() << endl;return 0;
}

MemoryPool.h

#ifndef MEMORY_POOL_H
#define MEMORY_POOL_H#include <cstddef>
#include <cstdint>
#include <type_traits>
#include <utility>
//ifndef MEMORY_POOL_H 文件是否被包含的唯一標識,避免該被重復引入template <typename T, size_t BlockSize = 4096>
class MemoryPool{
public://這里的MemoryPool是個分配器,因此要滿足STL接口的命名規范,如value_type,pointer,const_pointer...typedef T value_type;typedef T* pointer;typedef const T* const_pointer;typedef T& reference;typedef size_t size_type;typedef ptrdiff_t difference_type;// rebind模板,支持不同類型的allocatortemplate <typename U>struct rebind{using other = MemoryPool<U, BlockSize>;};public:MemoryPool() noexcept; //noexcept,發生錯誤直接中斷,不拋出異常~MemoryPool() noexcept;//分配內存,返回內存地址pointerpointer allocate(size_type n = 1, const_pointer hint = nullptr);void deallocate(pointer p, size_type n = 1);template <typename U, typename... Args>//構造函數,第一個參數放allocate分配的內存地址,二個參數放要存在這個內存中的東西void construct(U *p, Args &&...args);//Args &&...args是函數參數包template <typename U>//銷毀函數,傳入要釋放的內存地址void destroy(U *p);size_type max_size() const noexcept;//計算總共有多少個Slotprivate:// 聯合體,被占用的時候存的是數據,被釋放之后存的是下一個Slot的地址,被釋放之前每個Slot的地址是順序存放的,因此之間沒有也不用next指針聯系,被釋放之后統一將被釋放的Slot給freeSlot來管理,這個時候存的就是指針,后續還要從內存池中獲取內存的時候優先分配被釋放的Slot;union Slot_{//因為數據和指針在這里不需要同時存在,用union可以節省空間value_type element; // 存儲數據時使用Slot_ *next;        // 空閑時作為鏈表指針};//STL的語言風格,成員變量后面都會加一個下劃線typedef char*  data_pointer_;  // 原始內存指針(用于計算偏移)typedef Slot_* slot_pointer_; // Slot指針//這里解釋一下兩個指針之間的區別,data_pointer_是char*類型的,如果data_pointer_++就是加一個字節,而slot_pointer_是Slot_*類型的,slot_pointer_++就是加sizeof(Slot*)個字節,用前者就是為了在內存對齊(填充內存)的時候方便計算處理到底要填充多少個字節;// 關鍵指針(管理內存塊和空閑槽)slot_pointer_ currentBlock_; // Block鏈表頭指針slot_pointer_ currentSlot_;  // 當前Block的第一個可用Slotslot_pointer_ lastSlot_;     // 當前Block的最后一個可用Slotslot_pointer_ freeSlots_;    // 空閑Slot鏈表頭指針size_type padPointer(data_pointer_ p, size_type align) const noexcept;//計算要填充多少個字節void allocateBlock(); // 申請新的Block內存塊static_assert(BlockSize >= 2 * sizeof(Slot_), "BlockSize too small!");//C++11引入的編譯時斷言機制,當第一個參數(語句)為假,會輸出第二個參數,并且編譯中斷,如果用if的話要等到運行時才判斷,編譯時斷言機制可以在編譯的時候就中斷,提高效率;
};// 包含實現文件,模板類的特殊處理
#include "MemoryPool.tcc"#endif // MEMORY_POOL_H

tcc文件是模板實現文件
MemoryPool.tcc

// 構造和析構
template <typename T, size_t BlockSize>
MemoryPool<T, BlockSize>::MemoryPool() noexcept//這里實現MemoryPool類中的MemoryPoll()構造函數,地址初始化為空: currentBlock_(nullptr), currentSlot_(nullptr), lastSlot_(nullptr), freeSlots_(nullptr) {
} // 參數列表// 析構函數,釋放的是整個Block的內存
template <typename T, size_t BlockSize>
MemoryPool<T, BlockSize>::~MemoryPool() noexcept{slot_pointer_ curr = currentBlock_; // 這里的currentBlock_是MemoryPool類中的成員變量,因為已經進入到類的作用域內while (curr != nullptr){slot_pointer_ next = curr->next; // Block的next指針存儲的是前一個Block的地址operator delete(reinterpret_cast<void *>(curr)); // 釋放Block內存,此時的curr是懸空指針,operator delete區別于delete是operator delete釋放的是operator new分配的內存,delete釋放內存+自動調用析構函數,operator delete只釋放內存,不調用析構函數;operator delete要求接受一個void*類型的地址,因此用reinterpret_cast對curr類型轉換;curr = next;}
}// 內存池對齊計算(padPointer)
template <typename T, size_t BlockSize>
typename MemoryPool<T, BlockSize>::size_type // 返回類型是MemoryPool中的size_type,typename用于明確告訴編譯器,一個依賴于模板參數的名稱是一個類型,MemoryPool<T, BlockSize> 是一個模板類,其成員 size_type的定義依賴于模板參數T和BlockSize。在模板被實例化前,編譯器無法確定size_type是一個類型(如using size_type = size_t)還是一個靜態成員變量(如static size_t size_type)。
MemoryPool<T, BlockSize>::padPointer(data_pointer_ p, size_type align) const noexcept{// uintptr_t addr = reinterpret_cast<uintptr_t>(p);// return (addr - align) % align;uintptr_t addr = reinterpret_cast<uintptr_t>(p); // unitptr_t是一個存儲無符號地址(整數)的類型,方便地址計算(更安全)size_type remainder = addr % align;return remainder == 0 ? 0 : (align - remainder); // 這里的align是對齊目標,也就是說如果align=4,那么要求地址是4的整數倍,比如addr=13,align=4,那么(align - addr) % align = (4-13)% 4 = 1, 那么addr只需要補上(align-1)=3,即addr=13+3=16就是4的整數倍;
}// 申請新Block,當前Block已經沒有Slot可以分配的時候就申請新的Block(allocateBlock)
template <typename T, size_t BlockSize>
void MemoryPool<T, BlockSize>::allocateBlock(){// 先用data_pointer_再轉換成slot_pointer_,雖然可以直接寫成分配slot_pointer_,但是這樣寫語義更明確:先初始化內存塊,再結構化槽位data_pointer_ newBlock = reinterpret_cast<data_pointer_>(operator new(BlockSize));// operator new只分配內存,不構造對象,區別于new,new即分配內存也構造對象;operator new分配一個大小為BlockSize的內存// 將新的Block加入鏈表(頭插)slot_pointer_ blockHeader = reinterpret_cast<slot_pointer_>(newBlock);blockHeader->next = currentBlock_;currentBlock_ = blockHeader;// 計算Block主體部分的起始地址(跳過鏈表指針占用的Slot)data_pointer_ bodyStart = newBlock + sizeof(slot_pointer_); // newBlock是新的Block的起始地址,Header部分存的就是指向下一個Block的地址,即slot_pointer_,因此newBlock+sizeof(slot_pointer_) 偏移到Body的起始地址size_type align = alignof(slot_pointer_);// alignof獲取slot_pointer_的對齊要求,返回的是slot_pointer_及Slot_* 指針本身的對齊值,32位系統是4,64位系統是8size_type padding = padPointer(bodyStart, align); // 計算填充量// 確定可用Slot的范圍,currentSlot是內存對齊后可以真正存儲數據的slot的起始地址currentSlot_ = reinterpret_cast<slot_pointer_>(bodyStart + padding);data_pointer_ blockEnd = newBlock + BlockSize;lastSlot_ = reinterpret_cast<slot_pointer_>(blockEnd - sizeof(Slot_)); // 計算最后一個Slot的起始地址
}//內存分配
template <typename T, size_t BlockSize>
typename MemoryPool<T, BlockSize>::pointer // 返回類型是pointer
MemoryPool<T, BlockSize>::allocate(size_type n, const_pointer hint){// 處理連續分配請求if (n > 1){// 特殊處理連續內存請求(此處簡化實現,實際需考慮內存對齊)data_pointer_ newMem = reinterpret_cast<data_pointer_>(operator new(n * sizeof(Slot_)));return reinterpret_cast<pointer>(newMem);}// 單對象分配邏輯,優先從空閑鏈表獲取Slotif (freeSlots_ != nullptr){pointer result = reinterpret_cast<pointer>(freeSlots_);freeSlots_ = freeSlots_->next;return result; // 如果分配的是int*類型,那么此時result就是int*類型的地址}if (currentSlot_ >= lastSlot_){ // 如果當前Block無空閑Slot,檢查是否需要申請新BlockallocateBlock();}return reinterpret_cast<pointer>(currentSlot_++); // 順序加就是下個Slot的位置,這里的++就是+sizeof(currentSlot_)
}// 內存釋放
template <typename T, size_t BlockSize>
void MemoryPool<T, BlockSize>::deallocate(pointer p, size_type n){if (n > 1){operator delete(p); // 直接釋放整塊內存return;}if (p != nullptr){slot_pointer_ slot = reinterpret_cast<slot_pointer_>(p);// 將釋放的Slot加入空閑鏈表(頭插)slot->next = freeSlots_;freeSlots_ = slot;}
}// 對象構建與銷毀
template <typename T, size_t BlockSize>
template <typename U, typename... Args>
void MemoryPool<T, BlockSize>::construct(U *p, Args &&...args){// 使用placement new語法在已分配的內存上構造對象;// placement new語法格式: new (addressx) Type(arguments...),address是已分配了內存的地址,Type是對象類型,arguments是構造函數的參數new (p) U(std::forward<Args>(args)...); // 完美轉發參數,`std::forward<Args>(args)...`固定寫法
}template <typename T, size_t BlockSize>
template <typename U>
void MemoryPool<T, BlockSize>::destroy(U *p){p->~U(); // 顯式調用析構函數
}// 計算最大可用Slot數
template <typename T, size_t BlockSize>
typename MemoryPool<T, BlockSize>::size_type
MemoryPool<T, BlockSize>::max_size() const noexcept{// 無符號整數運算:-1轉換為size_type即是最大值,計算機以補碼的形式存儲數據,-1的補碼轉換成無符號是最大的size_type maxBlocks = static_cast<size_type>(-1) / BlockSize;// 單個Block可用Slot數 = (BlockSize - 鏈表指針占用) / Slot大小size_type slotsPerBlock = (BlockSize - sizeof(slot_pointer_)) / sizeof(Slot_);return slotsPerBlock * maxBlocks; // 總可用Slot數
}

運行結果:

*p1 = 42
Test finished.
棧用時: 0.0114187
內存池棧用時: 0.0306049
998999
998999

補充

為什么要加個U?
為了拓展性,U可能是T的子類;

class Animal {};  // T=Animal
class Cat : public Animal {};  // U=CatMemoryPool<Animal> pool;
Animal* p = pool.allocate();//比如這里分配的Animal的內存,可以放入Cat
pool.construct<Cat>(p);  // 在Animal的內存上構造Cat(多態)
容器名作用第二個模板參數是?
stack容器適配器底層容器(如 deque<T>
queue容器適配器底層容器
priority_queue容器適配器底層容器
vector順序容器分配器(如 allocator<T>
deque順序容器分配器
list順序容器分配器
操作作用是否調用析構函數適用場景
operator delete(p)僅釋放內存? 不調用底層內存管理(如內存池)
delete p釋放內存 + 調用析構函數? 調用普通對象釋放
delete[] p釋放數組內存 + 調用每個元素的析構函數? 調用對象數組釋放

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/82585.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/82585.shtml
英文地址,請注明出處:http://en.pswp.cn/web/82585.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【極客日常】分享go開發中wire和interface配合的一些經驗

在先前一篇文章中&#xff0c;筆者給大家提到了go語言后端編程可以用wire依賴注入模塊去簡化單例服務的初始化&#xff0c;同時也可以解決服務單例之間復雜依賴的問題。但實事求是來講&#xff0c;用wire也是有一些學習成本的&#xff0c;wire在幫助解決復雜依賴的問題同時&…

20250605車充安服務器受木馬攻擊導致服務不可用

https://mp.weixin.qq.com/s/2JyxmDIDBa9_owNjIJ6UIg 因業務服務器受木馬攻擊&#xff0c;服務器網絡資源損耗&#xff0c;業務負載能力受損

web3-虛擬合約 vs 現實合同:權利、義務與資產的鏈上新秩序

web3-虛擬合約 vs 現實合同&#xff1a;權利、義務與資產的鏈上新秩序 一、智能合約vs真實世界合約 傳統合約&#xff1a;基礎要素 如下圖&#xff0c;現實世界的合約&#xff0c;會有一個條款&#xff0c;然后下面還有一個“Alice”的簽名 提出合約和接受合約&#xff1b; …

【面經分享】京東

線程池核心參數 7 個參數。 coreSize maxSize 阻塞隊列 時間 時間 線程工廠 拒絕策略 核心參數的話&#xff0c;有 coreSize、阻塞隊列、拒絕策略。 JVM 組成 內存上劃分&#xff1a; 線程私有&#xff1a;Java 虛擬機棧&#xff0c;本地方法棧、Tlab、程序計數器 …

工作流引擎-11-開源 BPM 項目 jbpm

工作流引擎系列 工作流引擎-00-流程引擎概覽 工作流引擎-01-Activiti 是領先的輕量級、以 Java 為中心的開源 BPMN 引擎&#xff0c;支持現實世界的流程自動化需求 工作流引擎-02-BPM OA ERP 區別和聯系 工作流引擎-03-聊一聊流程引擎 工作流引擎-04-流程引擎 activiti 優…

深度學習在非線性場景中的核心應用領域及向量/張量數據處理案例,結合工業、金融等領域的實際落地場景分析

一、工業場景&#xff1a;非線性缺陷檢測與預測 1. ?半導體晶圓缺陷檢測? ?問題?&#xff1a;微米級劃痕、顆粒污染等缺陷形態復雜&#xff0c;與正常紋理呈非線性關系。?解決方案?&#xff1a; ?輸入張量?&#xff1a;高分辨率晶圓圖像 → 三維張量 (Batch, Height,…

Python-線程同步

多線程 案例 說明&#xff1a; 唱歌方法 sing()跳舞方法 dance()啟用兩個線程調用主線程結束 代碼 # 導入線程模塊 import threading import timedef sing(name,age):time.sleep(2)print(唱歌者姓名&#xff1a; name &#xff0c;年齡&#xff1a; str(age))print(正在唱…

前端八股之JS的原型鏈

1.原型的定義 每一個對象從被創建開始就和另一個對象關聯&#xff0c;從另一個對象上繼承其屬性&#xff0c;這個另一個對象就是 原型。 當訪問一個對象的屬性時&#xff0c;先在對象的本身找&#xff0c;找不到就去對象的原型上找&#xff0c;如果還是找不到&#xff0c;就去…

kafka命令

kafka安裝先安裝zookeeper&#xff0c;jdk 確保jdk版本與kafka版本匹配&#xff1a; 先啟動zookeeper&#xff1a; # 啟動獨立安裝的zookeeper ./zkServer.sh start # 也可以自動kafka自帶的zookerper ./zookeeper-server-start.sh ../config/zookeeper.pr…

微服務面試(分布式事務、注冊中心、遠程調用、服務保護)

1.分布式事務 分布式事務&#xff0c;就是指不是在單個服務或單個數據庫架構下&#xff0c;產生的事務&#xff0c;例如&#xff1a; 跨數據源的分布式事務跨服務的分布式事務綜合情況 我們之前解決分布式事務問題是直接使用Seata框架的AT模式&#xff0c;但是解決分布式事務…

Linux --進程優先級

概念 什么是進程優先級&#xff0c;為什么需要進程優先級&#xff0c;怎么做到進程優先級這是本文需要解釋清楚的。 優先級的本質其實就是排隊&#xff0c;為了去爭奪有限的資源&#xff0c;比如cpu的調度。cpu資源分配的先后性就是指進程的優先級。優先級高的進程有優先執行的…

React 性能監控與錯誤上報

核心問題與技術挑戰 現代 React 應用隨著業務復雜度增加&#xff0c;性能問題和運行時錯誤日益成為影響用戶體驗的關鍵因素。沒有可靠的監控與錯誤上報機制&#xff0c;我們將陷入被動修復而非主動預防的困境。 性能指標體系與錯誤分類 關鍵性能指標定義 // performance-me…

芒果深度學習檢測:開啟農業新視界(貓臉碼客第230期)

芒果深度學習檢測&#xff1a;開啟農業新視界 一、引言 芒果作為熱帶水果中的“明星”&#xff0c;在全球水果市場占據著重要地位&#xff0c;擁有廣泛的市場需求和可觀的經濟價值。伴隨人們生活品質的提升&#xff0c;對芒果品質的要求也愈發嚴苛。芒果產業規模持續擴張&#…

PDF文件轉換之輸出指定頁到新的 PDF 文件

背景 一份 PDF 學習資料需要打印其中某幾頁&#xff0c;文件有幾百兆&#xff0c;看到 WPS 有PDF拆分功能&#xff0c;但是需要會員&#xff0c;開了一個月會員后完成了轉換。突然想到&#xff0c;會員到期后如果還要拆解的話&#xff0c;怎么辦呢&#xff1f;PDF 文件拆解功能…

【計網】SW、GBN、SR、TCP

目錄 三種可靠傳輸機制&#xff08;數據鏈路層&#xff09; 停止-等待&#xff08;Stop and Wait&#xff0c;SW&#xff09;協議 回退N幀&#xff08;Go-back-N&#xff0c;GBN&#xff09;協議 選擇重傳&#xff08;Selective Repeat&#xff0c;SR&#xff09;協議 傳輸…

Go的隱式接口機制

正確使用Interface 不要照使用C/Java等OOP語言中接口的方式去使用interface。 Go的Interface的抽象不僅可以用于dynamic-dispatch 在工程上、它最大的作用是&#xff1a;隔離實現和抽象、實現完全的dependency inversion 以及interface segregation(SOLID principle中的I和D)。…

Async-profiler 內存采樣機制解析:從原理到實現

引言 在 Java 性能調優的工具箱中&#xff0c;async-profiler 是一款備受青睞的低開銷采樣分析器。它不僅能分析 CPU 熱點&#xff0c;還能精確追蹤內存分配情況。本文將深入探討 async-profiler 實現內存采樣的多種機制&#xff0c;結合代碼示例解析其工作原理。 為什么需要內…

Android 顏色百分比對照

本文就是簡單寫個demo,打印下顏色百分比的數值.方便以后使用. 1: 獲取透明色 具體的代碼如下: /*** 獲取透明色* param percent* param red* param green* param blue* return*/public static int getTransparentColor(int percent, int red, int green, int blue) {int alp…

MPLS-EVPN筆記詳述

目錄 EVPN簡介: EVPN路由: 基本四種EVPN路由 擴展: EVPN工作流程: 1.啟動階段: 2.流量轉發: 路由次序整理: 總結: EVPN基本術語: EVPN表項: EVPN支持的多種服務模式: 簡介: 1.Port Based: 簡介: 配置實現: 2.VLAN Based: 簡介: 配置實現: 3.VLAN Bundle: 簡…

SpringBoot自定義線程池詳細教程

文章目錄 1. 線程池基礎概念1.1 什么是線程池1.2 Java線程池核心參數1.3 線程池執行流程 2. SpringBoot中的線程池2.1 SpringBoot默認線程池2.2 SpringBoot異步任務基礎 3. 自定義線程池配置3.1 配置文件方式3.2 Java配置方式3.3 線程池工廠配置 4. 異步任務實際應用4.1 業務服…