高并發內存池的輕量級模擬-細節處理與優化部分

一.當申請的內存大小大于256kb的處理方式

? ? ? ? 因為256kb對于我們當前的實現其實也就32頁,我們的頁緩存上限是128頁.所以思路非常清晰明了:當申請內存大小大于32頁同時小于等于128頁時,我們按照一頁的方式向上對齊后計算所需頁數,然后向頁緩存申請.而大于128頁的請求我們直接向堆申請:

static void* ConcurrentAlloc(size_t size)
{if (size > MAX_BYTES){//當PageCache能滿足需要時,我們向PageCache申請,滿足不了直接向堆申請size_t pages = (AlignMoudle::AlignUpwards(size)) >> PAGE_SHIFT;PageCache::GetInstance()->_smutex.lock();Span* span = PageCache::GetInstance()->NewSpan(pages);PageCache::GetInstance()->_smutex.unlock();void* res = (void*)(span->_pageId << PAGE_SHIFT);return res;}else{//<MAX_BYTES時的邏輯if (pTLSThreadCache == nullptr){pTLSThreadCache = new ThreadCache;}return pTLSThreadCache->Allocate(size);}
}static void ConcurrentFree(void* ptr,size_t size)
{if (size > MAX_BYTES){Span* FreeSpan = PageCache::GetInstance()->MapObjectToSpan(ptr);PageCache::GetInstance()->_smutex.lock();PageCache::GetInstance()->ReleaseSpanToPageCache(FreeSpan);PageCache::GetInstance()->_smutex.unlock();}else{//<MAX_BYTES時的邏輯assert(pTLSThreadCache);pTLSThreadCache->Deallocate(ptr, size);}
}
Span* PageCache::NewSpan(size_t pos)
{if (pos >= MAX_PAGE){//此時頁緩存無法滿足需求,直接向堆進行申請void* ptr = SystemAlloc(pos);Span* BigSpan = new Span;BigSpan->_n = pos;BigSpan->_pageId = ((PAGE_ID)ptr) >> PAGE_SHIFT;_idmapspan[BigSpan->_pageId] = BigSpan;return BigSpan;}//<128頁均走原來的邏輯
...........................................................................................
void PageCache::ReleaseSpanToPageCache(Span* span)
{if (span->_n >= MAX_PAGE){void* ptr = (void*)(span->_pageId << PAGE_SHIFT);SystemFree(ptr);delete span;return;}//同上,<128頁的時候走原來的邏輯
...........................................................................................

二.使用對象池脫離使用new與Delete

? ? ? ? 因為原高并發內存池是通過某種技術脫離使用new與Delete的.而我們原來的代碼中涉及new與delete的地方一共有三個地方:PageCache.cpp申請新Span與釋放Span,comm.h中對于spanlist的頭結點的申請,以及每個線程池申請自己的pTLSThreadCache.

? ? ? ? 而這些地方都是對對象的申請,所以我們就可以使用對象池來替換他們.因為ppTLSThreadCache涉及到多線程競爭問題,所以我們需要對對象池的申請與釋放進行額外的加鎖,以避免多線程的競爭問題.對于comm.h應在構造函數處定義局部靜態對象池,不然如果是類靜態成員變量則需要額外的加鎖解鎖.

ly/Project-Code//可以從ConCurrentMemoryPool提交記錄中找到這部分的修改

三.釋放對象時優化為不傳對象大小

? ? ? ? 對于new與delete接口,delete釋放對象的時候是不需要顯示給對象大小的.但是我們能夠獲取釋放對象的地址.因此,我們可以在span中新增一個objsize對象,用于記錄當前span管理的頁面上分配的對象的大小:

static void* ConcurrentAlloc(size_t size)
{if (size > MAX_BYTES){//當PageCache能滿足需要時,我們向PageCache申請,滿足不了直接向堆申請size_t pages = (AlignMoudle::AlignUpwards(size)) >> PAGE_SHIFT;PageCache::GetInstance()->_smutex.lock();Span* span = PageCache::GetInstance()->NewSpan(pages);span->_objsize = size;PageCache::GetInstance()->_smutex.unlock();void* res = (void*)(span->_pageId << PAGE_SHIFT);return res;}else{//<MAX_BYTES時的邏輯if (pTLSThreadCache == nullptr){static  ObjectPool<ThreadCache> _threcpool;//pTLSThreadCache = new ThreadCache;pTLSThreadCache = _threcpool.New();}return pTLSThreadCache->Allocate(size);}
}static void ConcurrentFree(void* ptr)
{Span* FreeSpan = PageCache::GetInstance()->MapObjectToSpan(ptr);size_t size = FreeSpan->_objsize;if (size > MAX_BYTES){PageCache::GetInstance()->_smutex.lock();PageCache::GetInstance()->ReleaseSpanToPageCache(FreeSpan);PageCache::GetInstance()->_smutex.unlock();}else{//<MAX_BYTES時的邏輯assert(pTLSThreadCache);pTLSThreadCache->Deallocate(ptr, size);}
}//CentralCache::GetOneSpan
//走到這里說明當前中心緩存的該list哈希桶沒有空閑頁,則需要向頁緩存申請新頁,加全局鎖
PageCache::GetInstance()->_smutex.lock();
Span* span = PageCache::GetInstance()->NewSpan(AlignMoudle::NumMovePage(byte_size));
span->_isUse = true;
span->_objsize = byte_size;
PageCache::GetInstance()->_smutex.unlock();

四. 與malloc/free的性能對比

測試用例如下:

#include"ConcurrentAlloc.h"// ntimes 一輪申請和釋放內存的次數
// rounds 輪次
void BenchmarkMalloc(size_t ntimes, size_t nworks, size_t rounds)
{std::vector<std::thread> vthread(nworks);std::atomic<size_t> malloc_costtime = 0;std::atomic<size_t> free_costtime = 0;for (size_t k = 0; k < nworks; ++k){vthread[k] = std::thread([&, k]() {std::vector<void*> v;v.reserve(ntimes);for (size_t j = 0; j < rounds; ++j){size_t begin1 = clock();for (size_t i = 0; i < ntimes; i++){//v.push_back(malloc(16));v.push_back(malloc((16 + i) % 8192 + 1));}size_t end1 = clock();size_t begin2 = clock();for (size_t i = 0; i < ntimes; i++){free(v[i]);}size_t end2 = clock();v.clear();malloc_costtime += (end1 - begin1);free_costtime += (end2 - begin2);}});}for (auto& t : vthread){t.join();}printf("%u個線程并發執行%u輪次,每輪次malloc %u次: 花費:%u ms\n",nworks, rounds, ntimes, malloc_costtime.load());printf("%u個線程并發執行%u輪次,每輪次free %u次: 花費:%u ms\n",nworks, rounds, ntimes, free_costtime.load());printf("%u個線程并發malloc&free %u次,總計花費:%u ms\n",nworks, nworks * rounds * ntimes, malloc_costtime.load() + free_costtime.load());
}// 單輪次申請釋放次數 線程數 輪次
void BenchmarkConcurrentMalloc(size_t ntimes, size_t nworks, size_t rounds)
{std::vector<std::thread> vthread(nworks);std::atomic<size_t> malloc_costtime = 0;std::atomic<size_t> free_costtime = 0;for (size_t k = 0; k < nworks; ++k){vthread[k] = std::thread([&]() {std::vector<void*> v;v.reserve(ntimes);for (size_t j = 0; j < rounds; ++j){size_t begin1 = clock();for (size_t i = 0; i < ntimes; i++){//v.push_back(ConcurrentAlloc(16));v.push_back(ConcurrentAlloc((16 + i) % 8192 + 1));}size_t end1 = clock();size_t begin2 = clock();for (size_t i = 0; i < ntimes; i++){ConcurrentFree(v[i]);}size_t end2 = clock();v.clear();malloc_costtime += (end1 - begin1);free_costtime += (end2 - begin2);}});}for (auto& t : vthread){t.join();}printf("%u個線程并發執行%u輪次,每輪次concurrent alloc %u次: 花費:%u ms\n",nworks, rounds, ntimes, malloc_costtime.load());printf("%u個線程并發執行%u輪次,每輪次concurrent dealloc %u次: 花費:%u ms\n",nworks, rounds, ntimes, free_costtime.load());printf("%u個線程并發concurrent alloc&dealloc %u次,總計花費:%u ms\n",nworks, nworks * rounds * ntimes, malloc_costtime.load() + free_costtime.load());
}int main()
{size_t n = 10000;cout << "==========================================================" << endl;BenchmarkConcurrentMalloc(n, 4, 10);cout << endl << endl;BenchmarkMalloc(n, 4, 10);cout << "==========================================================" << endl;return 0;
}

?

五.性能瓶頸分析?

? ? ? ? 使用vs2022的性能分析工具,我們可以發現較為耗時的部分為查找映射表的函數部分,尤其是加鎖解鎖操作最為耗時.所以我們可以采取基數樹的方式進行優化,也就是去掉MapObjectToSpan中的加鎖解鎖操作.

使用基數樹進行優化

先來看效果:

再來看基數樹替換原來pagecache中哈希表的代碼:

#pragma once
#include"Comm.h"// Single-level array
template <int BITS>
class TCMalloc_PageMap1 {
private:static const int LENGTH = 1 << BITS;void** array_;public:typedef uintptr_t Number;//explicit TCMalloc_PageMap1(void* (*allocator)(size_t)) {explicit TCMalloc_PageMap1() {//array_ = reinterpret_cast<void**>((*allocator)(sizeof(void*) << BITS));size_t size = sizeof(void*) << BITS;size_t alignSize = AlignMoudle::_AlignUpwards(size, 1 << PAGE_SHIFT);array_ = (void**)SystemAlloc(alignSize >> PAGE_SHIFT);memset(array_, 0, sizeof(void*) << BITS);}// Return the current value for KEY.  Returns NULL if not yet set,// or if k is out of range.void* get(Number k) const {if ((k >> BITS) > 0) {return NULL;}return array_[k];}// REQUIRES "k" is in range "[0,2^BITS-1]".// REQUIRES "k" has been ensured before.//// Sets the value 'v' for key 'k'.void set(Number k, void* v) {array_[k] = v;}
};// Two-level radix tree
template <int BITS>
class TCMalloc_PageMap2 {
private:// Put 32 entries in the root and (2^BITS)/32 entries in each leaf.static const int ROOT_BITS = 5;static const int ROOT_LENGTH = 1 << ROOT_BITS;static const int LEAF_BITS = BITS - ROOT_BITS;static const int LEAF_LENGTH = 1 << LEAF_BITS;// Leaf nodestruct Leaf {void* values[LEAF_LENGTH];};Leaf* root_[ROOT_LENGTH];             // Pointers to 32 child nodesvoid* (*allocator_)(size_t);          // Memory allocatorpublic:typedef uintptr_t Number;//explicit TCMalloc_PageMap2(void* (*allocator)(size_t)) {explicit TCMalloc_PageMap2() {//allocator_ = allocator;memset(root_, 0, sizeof(root_));PreallocateMoreMemory();}void* get(Number k) const {const Number i1 = k >> LEAF_BITS;const Number i2 = k & (LEAF_LENGTH - 1);if ((k >> BITS) > 0 || root_[i1] == NULL) {return NULL;}return root_[i1]->values[i2];}void set(Number k, void* v) {const Number i1 = k >> LEAF_BITS;const Number i2 = k & (LEAF_LENGTH - 1);ASSERT(i1 < ROOT_LENGTH);root_[i1]->values[i2] = v;}bool Ensure(Number start, size_t n) {for (Number key = start; key <= start + n - 1;) {const Number i1 = key >> LEAF_BITS;// Check for overflowif (i1 >= ROOT_LENGTH)return false;// Make 2nd level node if necessaryif (root_[i1] == NULL) {//Leaf* leaf = reinterpret_cast<Leaf*>((*allocator_)(sizeof(Leaf)));//if (leaf == NULL) return false;static ObjectPool<Leaf>	leafPool;Leaf* leaf = (Leaf*)leafPool.New();memset(leaf, 0, sizeof(*leaf));root_[i1] = leaf;}// Advance key past whatever is covered by this leaf nodekey = ((key >> LEAF_BITS) + 1) << LEAF_BITS;}return true;}void PreallocateMoreMemory() {// Allocate enough to keep track of all possible pagesEnsure(0, 1 << BITS);}
};// Three-level radix tree
template <int BITS>
class TCMalloc_PageMap3 {
private:// How many bits should we consume at each interior levelstatic const int INTERIOR_BITS = (BITS + 2) / 3; // Round-upstatic const int INTERIOR_LENGTH = 1 << INTERIOR_BITS;// How many bits should we consume at leaf levelstatic const int LEAF_BITS = BITS - 2 * INTERIOR_BITS;static const int LEAF_LENGTH = 1 << LEAF_BITS;// Interior nodestruct Node {Node* ptrs[INTERIOR_LENGTH];};// Leaf nodestruct Leaf {void* values[LEAF_LENGTH];};Node* root_;                          // Root of radix treevoid* (*allocator_)(size_t);          // Memory allocatorNode* NewNode() {Node* result = reinterpret_cast<Node*>((*allocator_)(sizeof(Node)));if (result != NULL) {memset(result, 0, sizeof(*result));}return result;}public:typedef uintptr_t Number;explicit TCMalloc_PageMap3(void* (*allocator)(size_t)) {allocator_ = allocator;root_ = NewNode();}void* get(Number k) const {const Number i1 = k >> (LEAF_BITS + INTERIOR_BITS);const Number i2 = (k >> LEAF_BITS) & (INTERIOR_LENGTH - 1);const Number i3 = k & (LEAF_LENGTH - 1);if ((k >> BITS) > 0 ||root_->ptrs[i1] == NULL || root_->ptrs[i1]->ptrs[i2] == NULL) {return NULL;}return reinterpret_cast<Leaf*>(root_->ptrs[i1]->ptrs[i2])->values[i3];}void set(Number k, void* v) {ASSERT(k >> BITS == 0);const Number i1 = k >> (LEAF_BITS + INTERIOR_BITS);const Number i2 = (k >> LEAF_BITS) & (INTERIOR_LENGTH - 1);const Number i3 = k & (LEAF_LENGTH - 1);reinterpret_cast<Leaf*>(root_->ptrs[i1]->ptrs[i2])->values[i3] = v;}bool Ensure(Number start, size_t n) {for (Number key = start; key <= start + n - 1;) {const Number i1 = key >> (LEAF_BITS + INTERIOR_BITS);const Number i2 = (key >> LEAF_BITS) & (INTERIOR_LENGTH - 1);// Check for overflowif (i1 >= INTERIOR_LENGTH || i2 >= INTERIOR_LENGTH)return false;// Make 2nd level node if necessaryif (root_->ptrs[i1] == NULL) {Node* n = NewNode();if (n == NULL) return false;root_->ptrs[i1] = n;}// Make leaf node if necessaryif (root_->ptrs[i1]->ptrs[i2] == NULL) {Leaf* leaf = reinterpret_cast<Leaf*>((*allocator_)(sizeof(Leaf)));if (leaf == NULL) return false;memset(leaf, 0, sizeof(*leaf));root_->ptrs[i1]->ptrs[i2] = reinterpret_cast<Node*>(leaf);}// Advance key past whatever is covered by this leaf nodekey = ((key >> LEAF_BITS) + 1) << LEAF_BITS;}return true;}void PreallocateMoreMemory() {}
};

基數樹優化比原來快的根本原因是因為它避免了頻繁的加鎖解鎖,因為他是讀寫分離的(STL容器不保證線程安全問題).為什么是這樣,我們這里不再介紹,有興趣的讀者可以參考下面連接的文章:

查找——圖文翔解RadixTree(基數樹) - wgwyanfs - 博客園?

完整的基數樹優化代碼如下:

ConCurrentMemoryPool · ly/Project-Code - 碼云 - 開源中國?

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

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

相關文章

R語言速釋制劑QBD解決方案之三

本文是《Quality by Design for ANDAs: An Example for Immediate-Release Dosage Forms》第一個處方的R語言解決方案。 第一個處方研究評估原料藥粒徑分布、MCC/Lactose比例、崩解劑用量對制劑CQAs的影響。 第二處方研究用于理解顆粒外加硬脂酸鎂和滑石粉對片劑質量和可生產…

【Go語言基礎【19】】接口:靈活實現多態的核心機制

文章目錄 零、概述一、接口基礎1、接口的基本概念a. 接口定義b. 類型實現接口&#xff08;無需顯式聲明&#xff09;c. 接口變量&#xff08;體現了多態&#xff09; 2、實現接口的方式3、接口組合4、接口的底層結構 二、空接口與類型斷言1. 空接口&#xff08;interface{}&…

Linux文件管理和輸入輸出重定向

文件管理 Bash執行命令 passwd passwd普通用戶修改密碼 passwd robinkoolroot用戶管理賬戶密碼 passwd -d robinkoolroot用戶刪除普通用戶密碼 file file /bin/filecat cat option 文件 cat -A /etc/hosts #-A選項等于-VETcat /etc/hosts /etc/fstab一次性查看多個文件…

檢查項目中的依賴是否有更新——npm outdated

項目中輸入 npm outdated如果出現package紅色 則是需要更新的插件 更新最新的插件 使用latest下面的版本 Package Current Wanted Latest Location 包的名字 項目當前的版本 ... 需要更新到的版本然后將Latest的版本復制到pakcea…

vSphere環境ubuntu24.04虛擬機從BIOS切換為EFI模式啟動

文章目錄 一、操作背景二、操作步驟1.配置本地鏡像倉庫(可選)2.確認當前分區是gpt分區3.創建EFI分區4.安裝和修改GRUB5.重啟配置生效 三、驗證EFI模式方法 1&#xff1a;檢查 /sys/firmware/efi 目錄方法 2&#xff1a;檢查 dmesg 啟動日志方法 3&#xff1a;使用 efibootmgr&a…

python打卡day48

import torch # 生成一個3x3的標準正態分布隨機張量 random_tensor torch.randn(3, 3) print("隨機張量:\n", random_tensor) 隨機張量: tensor([[-0.9343, -0.3254, 0.6991], [-1.7157, 1.7171, -0.4322], [ 0.6004, -1.1050, -0.2178]]) # …

推薦算法八股總結

從計算機視覺轉行搜廣推的第9天 1.youtubednn 推薦系統經典模型YouTubeDNN_推薦系統架構圖-CSDN博客文章瀏覽閱讀2.1k次&#xff0c;點贊28次&#xff0c;收藏34次。本文詳細介紹了YouTubeDNN推薦系統&#xff0c;包括其召回階段的多模型篩選策略&#xff0c;排序階段的復雜模…

EasyRTC音視頻實時通話功能在WebRTC與智能硬件整合中的應用與優勢

一、WebRTC與智能硬件整合趨勢? 隨著物聯網和實時通信需求的爆發式增長&#xff0c;WebRTC作為開源實時通信技術&#xff0c;為瀏覽器與移動應用提供免插件的音視頻通信能力&#xff0c;在智能硬件領域的融合應用已成必然趨勢。智能硬件不再局限于單一功能&#xff0c;對實時…

零基礎在實踐中學習網絡安全-皮卡丘靶場(第九期-Unsafe Fileupload模塊)(yakit方式)

本期內容并不是很難&#xff0c;相信大家會學的很愉快&#xff0c;當然對于有后端基礎的朋友來說&#xff0c;本期內容更加容易了解&#xff0c;當然沒有基礎的也別擔心&#xff0c;本期內容會詳細解釋有關內容 本期用到的軟件&#xff1a;yakit&#xff08;因為經過之前好多期…

生信服務器 | 做生信為什么推薦使用Linux服務器?

原文鏈接&#xff1a;生信服務器 | 做生信為什么推薦使用Linux服務器&#xff1f; 原文鏈接&#xff1a;生信服務器 | 做生信為什么推薦使用Linux服務器&#xff1f; ---- 原文鏈接&#xff1a;生信服務器 | 做生信為什么推薦使用Linux服務器&#xff1f; ---- 原文鏈…

OpenCV 圖像色彩空間轉換與摳圖

一、知識點: 1、色彩空間轉換函數 (1)、void cvtColor( InputArray src, OutputArray dst, int code, int dstCn 0, AlgorithmHint hint cv::ALGO_HINT_DEFAULT ); (2)、將圖像從一種顏色空間轉換為另一種。 (3)、參數說明: src: 輸入圖像&#xff0c;即要進行顏…

高斯列主元消去法——python實現

高斯列主元消去法 1. 高斯消去法 高斯消去法是一種求解線性方程組 A x b A\mathbf{x} \mathbf{b} Axb 的方法&#xff0c;通過逐步化簡增廣矩陣&#xff0c;將其變為上三角矩陣&#xff0c;從而方便求解未知數。 線性方程組的一般形式為&#xff1a; { a 11 x 1 a 12 x…

linux下安裝elasticsearch及ik分詞器

linux下安裝elasticsearch及ik分詞器 安裝版本 linux版本&#xff1a;centos7.5 es版本&#xff1a;elasticsearch-7.14.0-linux-x86_64.tar.gz 下載地址&#xff1a;https://www.elastic.co/downloads/past-releases#elasticsearch Ik版本&#xff1a;elasticsearch-analysi…

相機Camera日志分析之三十一:高通Camx HAL十種流程基礎分析關鍵字匯總(后續持續更新中)

【關注我,后續持續新增專題博文,謝謝!!!】 上一篇我們講了:有對最普通的場景進行各個日志注釋講解,但相機場景太多,日志差異也巨大。后面將展示各種場景下的日志。 通過notepad++打開場景下的日志,通過下列分類關鍵字搜索,即可清晰的分析不同場景的相機運行流程差異…

【配置篇】告別硬編碼:多環境配置、@ConfigurationProperties與配置中心初探

摘要 本文是《Spring Boot 實戰派》系列的第五篇&#xff0c;聚焦于企業級應用開發中至關重要的配置管理。文章將首先解決開發、測試、生產環境配置不同的痛點&#xff0c;詳細介紹 Spring Boot 的 Profile&#xff08;多環境配置&#xff09; 機制。接著&#xff0c;我們將深…

代碼隨想錄算法訓練營第60期第六十三天打卡

大家好&#xff0c;我們昨天講解的是拓撲排序與Dijkstra算法的樸素版&#xff0c;其實我們大致了解了兩種算法的代碼實現&#xff0c;我們通過上次博客了解到拓撲排序其實是可以判斷圖里是否存在環&#xff0c;而Dijkstra算法則使用于非負邊權最短路的求解&#xff0c;今天我們…

linux中如何在日志里面檢索nowStage不等于1的數據的指令

你想在 Linux 中查找日志文件中 nowStage 不等于 1 的所有 JSON 行&#xff0c;當前你已經使用了&#xff1a; Bash 深色版本 grep -rn "nowStage" ./ 這個命令可以找到包含 "nowStage" 字樣的所有行及其所在的文件名和行號&#xff0c;但還不能篩選出 no…

【習題】DevEco Studio的使用

判斷題 1. 如果代碼中涉及到一些網絡、數據庫、傳感器等功能的開發&#xff0c;均可使用預覽器進行預覽。 正確(True) 錯誤(False) 正確答案: 錯誤(False) 知識點 預覽器的使用。解析&#xff1a;預覽器只支持對頁面的預覽&#xff0c;如果代碼中涉及到一些網絡、數據庫、…

SpringBoot實現簡易直播

當下直播技術已經成為各類應用不可或缺的一部分&#xff0c;從社交媒體到在線教育&#xff0c;再到電子商務和游戲領域&#xff0c;直播功能正在被廣泛應用。 本文將介紹如何使用SpringBoot框架構建一個直播流推拉系統。 一、直播技術基礎 1.1 推流與拉流概念 直播系統的核心…

xcode 各版本真機調試包下載

下載地址 https://github.com/filsv/iOSDeviceSupport 使用方法&#xff1a; 添加到下面路徑中&#xff0c;然后退出重啟xcode /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport