【lesson11】高并发内存池性能优化

文章目录

  • 高并发内存池性能问题
  • 基数树优化性能
    • 代码
      • 一层基数树
      • 两层基数树
      • 三层基数树
  • 一层基数树替代map
    • PageCache.h
    • PageCache.cpp
    • 基数树线程安全的原因

高并发内存池性能问题

我们知道,我们实现的高并发内存池存在大量的申请锁和,释放锁,而这样就会导致我们的性能比不上原来的malloc
在这里插入图片描述

性能分析:
在这里插入图片描述
通过报告,我们发现性能差的很大原因是因为MapObjectToSpan
而MapObjectToSpan耗费性能的原因是因为锁的问题,频繁的申请锁和释放锁会很耗费性能。

// 获取从对象到span的映射
Span* PageCache::MapObjectToSpan(void* obj)
{PAGE_ID id = ((PAGE_ID)obj >> PAGE_SHIFT);std::unique_lock<std::mutex> lock(_pageMtx);auto ret = _idSpanMap.find(id);if (ret != _idSpanMap.end()){return ret->second;}else{assert(false);return nullptr;}
}

而这是我们就要对其进行优化,我们查看tcmalloc发现,他用一个叫基数树的数据结构解决了这方面的问题。
基数树也是存储id和span的映射关系。

基数树优化性能

代码

一层基数树

#pragma once
#include"Common.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 = SizeClass::_RoundUp(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;}
};

一层基数树,是在映射之前直接开辟219个指针大小的空间。
所以每个位置都能存储指针,而要存的_pageid直接映射到桶对应的下标处
在这里插入图片描述

两层基数树

#pragma once
#include"Common.h"
// 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);}
};

两层基数树和一层基数树,有所不同,
一层基数树是直接无脑开219个指针大小的空间,无论存不存在映射关系或,也不管映射关系的多与少。
两层基数树则是先开25个指针大小的空间
在这里插入图片描述
然后这时如果有映射关系,要插入其中,我们再开辟219-5个指针大小的空间也就是214个。

在这里插入图片描述
这样我们就比之前节省了一些空间。

在这里插入图片描述

三层基数树

#pragma once
#include"Common.h"
// 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() {}
};

和两次思路一样。

这里我们只使用一层基数树代替map。其余有兴趣自己实现。

一层基数树替代map

用到存储map映射关系,和查找map映射关系的函数,都只在Page Cache中,所以我们只在Page Cache中修改即可。

PageCache.h

#pragma once#include "Common.h"
#include "ObjectPool.h"
#include "PageMap.h"class PageCache
{
public:static PageCache* GetInstance(){return &_sInst;}// 获取从对象到span的映射Span* MapObjectToSpan(void* obj);// 释放空闲span回到Pagecache,并合并相邻的spanvoid ReleaseSpanToPageCache(Span* span);// 获取一个K页的spanSpan* NewSpan(size_t k);std::mutex _pageMtx;
private:SpanList _spanLists[NPAGES];ObjectPool<Span> _spanPool;//std::unordered_map<PAGE_ID, Span*> _idSpanMap;//std::map<PAGE_ID, Span*> _idSpanMap;TCMalloc_PageMap1<32 - PAGE_SHIFT> _idSpanMap;PageCache(){}PageCache(const PageCache&) = delete;static PageCache _sInst;
};

PageCache.cpp

#include "PageCache.h"PageCache PageCache::_sInst;// 获取一个K页的span
Span* PageCache::NewSpan(size_t k)
{assert(k > 0);// 大于128 page的直接向堆申请if (k > NPAGES-1){void* ptr = SystemAlloc(k);//Span* span = new Span;Span* span = _spanPool.New();span->_pageId = (PAGE_ID)ptr >> PAGE_SHIFT;span->_n = k;//_idSpanMap[span->_pageId] = span;_idSpanMap.set(span->_pageId, span);return span;}// 先检查第k个桶里面有没有spanif (!_spanLists[k].Empty()){Span* kSpan = _spanLists[k].PopFront();// 建立id和span的映射,方便central cache回收小块内存时,查找对应的spanfor (PAGE_ID i = 0; i < kSpan->_n; ++i){//_idSpanMap[kSpan->_pageId + i] = kSpan;_idSpanMap.set(kSpan->_pageId + i, kSpan);}return kSpan;}// 检查一下后面的桶里面有没有span,如果有可以把他它进行切分for (size_t i = k+1; i < NPAGES; ++i){if (!_spanLists[i].Empty()){Span* nSpan = _spanLists[i].PopFront();//Span* kSpan = new Span;Span* kSpan = _spanPool.New();// 在nSpan的头部切一个k页下来// k页span返回// nSpan再挂到对应映射的位置kSpan->_pageId = nSpan->_pageId;kSpan->_n = k;nSpan->_pageId += k;nSpan->_n -= k;_spanLists[nSpan->_n].PushFront(nSpan);// 存储nSpan的首位页号跟nSpan映射,方便page cache回收内存时// 进行的合并查找//_idSpanMap[nSpan->_pageId] = nSpan;//_idSpanMap[nSpan->_pageId + nSpan->_n - 1] = nSpan;_idSpanMap.set(nSpan->_pageId, nSpan);_idSpanMap.set(nSpan->_pageId + nSpan->_n - 1, nSpan);// 建立id和span的映射,方便central cache回收小块内存时,查找对应的spanfor (PAGE_ID i = 0; i < kSpan->_n; ++i){//_idSpanMap[kSpan->_pageId + i] = kSpan;_idSpanMap.set(kSpan->_pageId + i, kSpan);}return kSpan;}}// 走到这个位置就说明后面没有大页的span了// 这时就去找堆要一个128页的span//Span* bigSpan = new Span;Span* bigSpan = _spanPool.New();void* ptr = SystemAlloc(NPAGES - 1);bigSpan->_pageId = (PAGE_ID)ptr >> PAGE_SHIFT;bigSpan->_n = NPAGES - 1;_spanLists[bigSpan->_n].PushFront(bigSpan);return NewSpan(k);
}Span* PageCache::MapObjectToSpan(void* obj)
{PAGE_ID id = ((PAGE_ID)obj >> PAGE_SHIFT);/*std::unique_lock<std::mutex> lock(_pageMtx);auto ret = _idSpanMap.find(id);if (ret != _idSpanMap.end()){return ret->second;}else{assert(false);return nullptr;}*/auto ret = (Span*)_idSpanMap.get(id);assert(ret != nullptr);return ret;
}void PageCache::ReleaseSpanToPageCache(Span* span)
{// 大于128 page的直接还给堆if (span->_n > NPAGES-1){void* ptr = (void*)(span->_pageId << PAGE_SHIFT);SystemFree(ptr);//delete span;_spanPool.Delete(span);return;}// 对span前后的页,尝试进行合并,缓解内存碎片问题while (1){PAGE_ID prevId = span->_pageId - 1;//auto ret = _idSpanMap.find(prevId); 前面的页号没有,不合并了//if (ret == _idSpanMap.end())//{//	break;//}auto ret = (Span*)_idSpanMap.get(prevId);if (ret == nullptr){break;}// 前面相邻页的span在使用,不合并了Span* prevSpan = ret;if (prevSpan->_isUse == true){break;}// 合并出超过128页的span没办法管理,不合并了if (prevSpan->_n + span->_n > NPAGES-1){break;}span->_pageId = prevSpan->_pageId;span->_n += prevSpan->_n;_spanLists[prevSpan->_n].Erase(prevSpan);//delete prevSpan;_spanPool.Delete(prevSpan);}// 向后合并while (1){PAGE_ID nextId = span->_pageId + span->_n;/*auto ret = _idSpanMap.find(nextId);if (ret == _idSpanMap.end()){break;}*/auto ret = (Span*)_idSpanMap.get(nextId);if (ret == nullptr){break;}Span* nextSpan = ret;if (nextSpan->_isUse == true){break;}if (nextSpan->_n + span->_n > NPAGES-1){break;}span->_n += nextSpan->_n;_spanLists[nextSpan->_n].Erase(nextSpan);//delete nextSpan;_spanPool.Delete(nextSpan);}_spanLists[span->_n].PushFront(span);span->_isUse = false;//_idSpanMap[span->_pageId] = span;//_idSpanMap[span->_pageId+span->_n-1] = span;_idSpanMap.set(span->_pageId, span);_idSpanMap.set(span->_pageId + span->_n - 1, span);
}

基数树线程安全的原因

为什么基数树是线程安全的不用加锁?
在这里插入图片描述

Realese下再次对比性能:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/675939.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

为什么要进行FTP替代?专业的FTP替代方案了解一下!

FTP&#xff08;File Transfer Protocol&#xff0c;文件传输协议&#xff09;的历史可以追溯到20世纪70年代&#xff0c;这是一个由美国国防部资助的早期计算机网络&#xff0c;后来发展成为互联网的前身。随着时间的推移&#xff0c;FTP经历了多次迭代和改进&#xff0c;以适…

java多线程的四种创建方式、程序、线程、进程、并行、串行、Thread、Runnable、Callable、线程池技术

这里写目录标题 Java多线程相关概念使用多线程的优点&#xff1a;创建方式一&#xff1a;创建方式二&#xff1a;创建方式三&#xff1a;创建方式四&#xff1a; Java多线程相关概念 程序(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码&…

一条 SQL 查询语句是如何执行的

MySQL 的基本架构示意图 大体来说&#xff0c;MySQL 可以分为 Server 层和存储引擎层两部分 Server 层包括连接器、查询缓存、分析器、优化器、执行器等&#xff0c;涵盖 MySQL 的大多数核心服务功能&#xff0c;以及所有的内置函数&#xff08;如日期、时间、数学和加密函数等…

数字图像处理实验记录七(彩色图像处理实验)

一、基础知识 经过前面的实验可以得知&#xff0c;彩色图像中的RGB图像就是一个三维矩阵&#xff0c;有3个维度&#xff0c;它们分别存储着R元素&#xff0c;G元素&#xff0c;B元素的灰度信息&#xff0c;最后将它们合起来&#xff0c;便是彩色图像。 这一次实验涉及CMYK和HS…

深入了解微服务架构:原理、优势与实践指南

深入了解微服务架构&#xff1a;原理、优势与实践指南 I. 引言 A. 微服务架构的兴起 随着互联网和软件开发领域的不断发展&#xff0c;传统的单体架构在应对日益复杂的业务需求和快速变化的市场环境时逐渐显露出局限性。在这样的背景下&#xff0c;微服务架构应运而生&#…

Linux下的多用户管理和认证:从入门到精通(附实例)

Linux操作系统以其强大的多用户管理和认证机制而著称。这种机制不仅允许多个用户同时登录并执行各种任务&#xff0c;还能确保每个用户的数据安全和隐私。本文将通过一系列实例&#xff0c;带你逐步掌握Linux下的多用户管理和认证。 一、Linux多用户管理的基础知识 在Linux中&…

企业内部知识库管理软件的终极指南:如何选择最适合你的工具?

知识库管理软件对于希望提高客户支持和组织效率的公司来说是一个强大的工具。在数字时代&#xff0c;拥有一个可靠的知识库系统对于快速准确地满足客户需求至关重要。在当今的技术条件下&#xff0c;知识库管理软件有很多选择&#xff0c;每个企业都应该仔细评估并选择最适合自…

服务器与电脑的区别?

目录 一、什么是服务器 二、什么是电脑 三、服务器和电脑的区别 一、什么是服务器 服务器是指一种专门提供计算和存储资源、运行特定软件服务的物理或虚拟计算机。服务器主要用于接受和处理来自客户端&#xff08;如个人电脑、手机等&#xff09;的请求&#xff0c;并向客户…

Android:Android Studio安装及环境配置

1开发环境搭建 Android开发需要使用java的jdk环境,所以需要下载JAVA JDK。 1.1安装配置JAVA JDK Java的JDK下载: https://www.oracle.com/technetwork/java/javase/downloads/index.html 配置java的环境变量: JAVA_HOME:java安装路径。 新增环境变量CLASSPATH 在Path环境…

数据结构入门(1)数据结构介绍

目录 前言 1. 什么是数据结构&#xff1f; 2.什么是算法&#xff1f; 3.数据结构和算法的重要性 前言 本文将开始介绍计算机里的数据结构。 数据结构是指数据对象中元素之间的关系&#xff0c;以及对这些关系的操作。数据结构可以分为线性结构和非线性结构。 线性结构是…

【SQL】力扣1445. 苹果和桔子

1445. 苹果和桔子 【SQL】力扣1445. 苹果和桔子 在本篇博客中&#xff0c;我们将深入探讨力扣&#xff08;LeetCode&#xff09;数据库题目1445 —— "苹果和桔子"的详细解题步骤。我们会分析题目需求&#xff0c;然后详细解释SQL查询的构建过程&#xff0c;并对其…

C++ .h文件类的调用

demo1只有类的情况下调用 下面写一个util.h 文件里面 // 定义宏防止编译器重复编译 #ifndef TEST_H #define TEST_H class Test{ public:void sum(int a, int b);int num(int a, int b);bool number();}; #endif // TEST_H 调用的时候首先要引入这个头文件 #include "u…

华为机试真题实战应用【赛题代码篇】-等差数列(附Java、C++和python代码)

目录 题目描述 知识储备 等差数列 思路解析 代码实现 C++ 代码2

openGauss学习笔记-216 openGauss性能调优-确定性能调优范围-硬件瓶颈点分析-CPU

文章目录 openGauss学习笔记-216 openGauss性能调优-确定性能调优范围-硬件瓶颈点分析-CPU216.1 CPU216.2 查看CPU状况216.3 性能参数分析 openGauss学习笔记-216 openGauss性能调优-确定性能调优范围-硬件瓶颈点分析-CPU 获取openGauss节点的CPU、内存、I/O和网络资源使用情况…

[144] 二叉树的前序遍历 js

题目描述&#xff1a;给你二叉树的根节点 root &#xff0c;返回它节点值的 前序 遍历。 输入&#xff1a;root [1,null,2,3] * 输出&#xff1a;[1,2,3] 解题思路&#xff1a; 1 递归 2 迭代 前序 根左右 按照 根右左的顺序入栈&#xff0c;因为先进后出 每个节点出栈的…

Leetcode 213 打家劫舍 II

题意理解&#xff1a; 你是一个专业的小偷&#xff0c;计划偷窃沿街的房屋&#xff0c;每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 &#xff0c;这意味着第一个房屋和最后一个房屋是紧挨着的。同时&#xff0c;相邻的房屋装有相互连通的防盗系统&#xff0c;如果…

2024年华为OD机试真题-灰度图恢复-Java-OD统一考试(C卷)

题目描述: 黑白图像常采用灰度图的方式存储,即图像的每个像素填充一个灰阶值,256阶灰度图是一个灰阶值取值范围为0-255的灰阶矩阵,0表示全黑、255表示全白,范围内的其他值表示不同的灰度。但在计算机中实际存储时,会使用压缩算法,其中一种压缩格式和描述如下: 10 10 25…

项目02《游戏-11-开发》Unity3D

基于 项目02《游戏-10-开发》Unity3D &#xff0c; 任务&#xff1a;飞行坐骑 首先创建脚本&#xff0c; 绑定脚本&#xff0c; using UnityEngine; public class Dragon : MonoBehaviour{ [SerializeField] private float speed 10f; public Transfo…

TCP/IP协议以及UDP(超详细,看这一篇就够了)

&#x1f493; 博客主页&#xff1a;从零开始的-CodeNinja之路 ⏩ 收录专栏&#xff1a;TCP/IP协议以及UDP(超详细,看这一篇就够了) &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 TCP/IP协议以及UDP(超详细,看这一篇就够了 前提概括接收端和发送端客户…

【Linux】Linux开发工具(yum、gdb、git)详解

一、软件包管理器 yum 1、什么是软件包 在 Linux 下安装软件&#xff0c;通常的办法是下载到程序的源代码&#xff0c;并进行编译&#xff0c;得到可执行程序。但这样太麻烦了&#xff0c;于是有些人把一些常用的软件提前编译好&#xff0c;做成软件包&#xff08;可以理解成…