从零开始实现一个可靠、健壮的内存池

文章目录

    • 概要
      • 这个项目是干什么的
      • 项目所需储备知识
    • 什么是内存池
      • 池化技术
      • 内存池
      • 内存池主要解决的问题
    • 框架设计
    • 开发计划
    • 系统测试情况
    • 遇到的主要问题和解决方法
    • 分工和协作
    • 提交仓库目录和文件描述
    • 比赛收获

概要

这个项目是干什么的

当前项目是实现一个高并发的内存池,他的原型是google的一个开源项目tcmalloc,tcmalloc全称 Thread-Caching Malloc,即线程缓存的malloc,实现了高效的多线程内存管理,用于替代系统的内存 分配相关的函数(malloc、free)。 这个项目是把tcmalloc最核心的框架简化后拿出来,模拟实现出一个自己的高并发内存池,目的就 是学习tcamlloc的精华,这种方式有点类似我们之前学习STL容器的方式。


项目所需储备知识

这个项目会用到C/C++、数据结构(链表、哈希桶)、操作系统内存管理、单例模式、多线程、互斥锁 等等方面的知识。


什么是内存池

池化技术

所谓“池化技术”,就是程序先向系统申请过量的资源,然后自己管理,以备不时之需。之所以要申请过 量的资源,是因为每次申请该资源都有较大的开销,不如提前申请好了,这样使用时就会变得非常快 捷,大大提高程序运行效率。 在计算机中,有很多使用“池”这种技术的地方,除了内存池,还有连接池、线程池、对象池等。以服务 器上的线程池为例,它的主要思想是:先启动若干数量的线程,让它们处于睡眠状态,当接收到客户端 的请求时,唤醒池中某个睡眠的线程,让它来处理客户端的请求,当处理完这个请求,线程又进入睡眠 状态。

内存池

内存池是指程序预先从操作系统申请一块足够大内存,此后,当程序中需要申请内存的时候,不是直接 向操作系统申请,而是直接从内存池中获取;同理,当程序释放内存的时候,并不真正将内存返回给操 作系统,而是返回内存池。当程序退出(或者特定时间)时,内存池才将之前申请的内存真正释放。

内存池主要解决的问题

内存池解决的主要是效率及内存碎片问题。内存碎片分为内碎片/外碎片。

外部碎片是一些空闲的 连续内存区域太小,这些内存空间不连续,以至于合计的内存足够,但是不能满足一些的内存分配申请 需求。内部碎片是由于一些对齐的需求,导致分配出去的空间中一些内存无法被利用。


架构设计

现代很多的开发环境都是多核多线程,在申请内存的场景下,必然存在激烈的锁竞争问题。malloc本身 其实已经很优秀,那么我们项目的原型tcmalloc就是在多线程高并发的场景下更胜一筹,所以这次我们 实现的内存池需要考虑以下几方面的问题。

1. 性能问题。

2. 多线程环境下,锁竞争问题。

3. 内存碎片问题。

 concurrent memory pool主要由以下3个部分构成:

1. thread cache:线程缓存是每个线程独有的,用于小于256KB的内存的分配,线程从这里申请内 存不需要加锁,每个线程独享一个cache,这也就是这个并发线程池高效的地方。

2. central cache:中心缓存是所有线程所共享,thread cache是按需从central cache中获取的对 象。central cache合适的时机回收thread cache中的对象,避免一个线程占用了太多的内存,而 其他线程的内存吃紧,达到内存分配在多个线程中更均衡的按需调度的目的。central cache是存 在竞争的,所以从这里取内存对象是需要加锁,首先这里用的是桶锁,其次只有thread cache的 没有内存对象时才会找central cache,所以这里竞争不会很激烈。

3. page cache:页缓存是在central cache缓存上面的一层缓存,存储的内存是以页为单位存储及分 配的,central cache没有内存对象时,从page cache分配出一定数量的page,并切割成定长大小 的小块内存,分配给central cache。当一个span的几个跨度页的对象都回收以后,page cache 会回收central cache满足条件的span对象,并且合并相邻的页,组成更大的页,缓解内存碎片 的问题。

 


开发计划

开发计划为

1、先实现由ThreadCache至PageCache的内存申请过程。再进行简单的调试,对申请内存进行联调

2、再实现由ThreadCache至PageCache的内存释放过程,进行简单的调试,对释放内存进行联调

3、进行性能测试

4、思考优化,如利用定长内存池代替其中的new,delete操作,释放内存时不用带对象大小等


系统测试情况

以下为多线程并发环境下,对比malloc和ConcurrentAlloc申请和释放内存效率对比

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);
 printf("%u个线程并发执行%u轮次,每轮次free %u次: 花费:%u ms\n",
 nworks, rounds, ntimes, free_costtime);
 printf("%u个线程并发malloc&free %u次,总计花费:%u ms\n",
 nworks, nworks*rounds*ntimes, malloc_costtime + free_costtime);
}

遇到的主要问题和解决方法

问题

  1. 多线程环境下的锁竞争问题:多个线程同时访问内存池时,如何减少锁竞争以提高性能。
  2. 平台及兼容性在不同操作系统和架构下,内存分配和管理的差异可能导致兼容性问题。
  3. 内存池自身数据结构的管理:内存池自身数据结构(如SpanList中的span等)的管理也可能使用到malloc,没有完全脱离malloc。

解决办法

  1. 减少锁竞争
    • 通过为每个线程分配独立的threadCache,减少多线程环境下的锁竞争。
    • 在centralCache中使用桶锁(bucket lock)等技术,进一步减少锁竞争。
  2. 平台及兼容性处理
    • 根据不同平台和架构的特性,选择合适的内存分配和管理策略。
    • 在Linux等系统下,将某些特定的内存分配函数(如VirtualAlloc)替换为brk等。
  3. 内存池自身数据结构的管理
    • 对于内存池自身数据结构的管理,尽量减少使用malloc和new,可以考虑使用其他方式(如virtual alloc、brk、mmap等)来申请大块内存,并使用对象池等技术来管理小块内存。
    • 在64位系统下,对于某些数据结构(如map<id, Span*>)可能存在的性能和内存问题,可以考虑使用基数树等更高效的数据结构进行替换。

分工和协作

 项目规划与定义

  • 项目经理
    • 定义项目目标、范围和里程碑。
    • 制定项目计划和时间表。
    • 分配资源和任务给团队成员。
  • 架构师
    • 设计项目的整体架构,包括内存池的设计、线程缓存(ThreadCache)、中心缓存(CentralCache)和页缓存(PageCache)的交互方式。
    • 评估技术选型,确保所选技术栈能够支持高并发场景。

2. 编码实现

  • 核心开发团队
    • 负责实现内存池的核心功能,如内存的申请、分配、释放和合并。
    • 编写单元测试,确保每个模块的正确性。
    • 协作进行代码审查,提高代码质量。
    分工示例
    • 开发者A:负责ThreadCache的实现,优化线程间的内存访问性能。
    • 开发者B:负责CentralCache的实现,确保多个线程能够高效共享内存资源。
    • 开发者C:负责PageCache的实现,处理大内存块的分配和回收。
  • 性能测试团队
    • 设计并执行性能测试,评估内存池的性能和并发能力。
    • 根据测试结果提供优化建议。

3. 并发与锁优化

  • 并发控制专家
    • 负责优化多线程环境下的锁竞争问题,提高内存池的并发性能。
    • 研究并使用先进的并发控制算法,如无锁编程技术。

提交仓库目录和文件描述

文件描述

三层缓存结构

        ThreadCache层:ThreadCache.h,ThreadCache.cpp

        CentralCache层:CentralCache.h,CentralCache.cpp

        PageCache层:PageCache.h,PageCache,cpp

用于所有文件公用的类/变量

        common.h

用于替代new/delete的定长内存池

        ObjectPool.h

用于进行性能测试的文件

        Benchmark.cpp

核心接口

        ConcurrentAlloc.cpp

比赛收获

1. 深入理解高并发与内存管理

  • 通过设计高并发内存池,我深入理解了在高并发环境下,如何有效地管理内存资源,包括内存的分配、回收和复用,以及如何在多线程环境中确保内存操作的安全性和高效性。
  • 我学会了如何分析并解决内存碎片化问题,这对于提高系统的性能和稳定性至关重要。

2. 掌握了先进的并发控制技术

  • 在设计过程中,我接触并掌握了多种先进的并发控制技术,如无锁编程、锁分离、读写锁等,这些技术对于提高内存池的并发性能至关重要。
  • 我学会了如何根据具体的业务场景和需求,选择合适的并发控制技术,以达到最佳的性能和效率。

3. 提升了系统设计和架构能力

  • 设计高并发内存池需要综合考虑多个方面,包括系统的整体架构、模块划分、接口设计、线程模型等。通过这次经历,我提升了系统设计和架构能力,学会了如何构建一个高性能、可扩展、易维护的系统。
  • 我学会了如何平衡系统的复杂性和性能之间的关系,以及如何在设计过程中考虑系统的可测试性和可维护性。

4. 加强了团队协作和沟通能力

  • 在设计高并发内存池的过程中,我与团队成员进行了密切的协作和沟通,共同解决了许多技术难题。这次经历加强了我的团队协作和沟通能力,使我更加擅长与团队成员合作,共同完成任务。
  • 我学会了如何有效地表达自己的观点和想法,以及如何倾听他人的意见和建议,这对于提高团队的凝聚力和工作效率至关重要。

5. 增强了解决问题的能力

  • 在设计过程中,我遇到了许多复杂的问题和挑战,如内存泄漏、死锁、性能瓶颈等。通过不断地尝试和探索,我逐渐学会了如何分析问题、定位问题并解决问题。
  • 这次经历增强了我的解决问题的能力,使我更加自信和从容地面对未来的技术挑战。

6. 拓展了技术视野和知识面

  • 在设计高并发内存池的过程中,我接触了许多新的技术和工具,如高性能数据结构、并发编程库、性能测试工具等。这些新的技术和工具拓展了我的技术视野和知识面,使我对计算机系统的底层原理和性能优化有了更深入的理解。

总之,参加这次比赛是一次非常有价值的经历。通过这次经历,我不仅提升了自己的技术能力和团队协作能力,还拓展了自己的技术视野和知识面。我相信这些收获将对我未来的职业发展产生积极的影响。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

养生与健康|一起跟随林曦老师养个元气满满

暄桐是一间传统美学教育教室&#xff0c;创办于2011年&#xff0c;林曦是创办人和授课老师&#xff0c;教授以书法为主的传统文化和技艺&#xff0c;皆在以书法为起点&#xff0c;亲近中国传统之美&#xff0c;以实践和所得&#xff0c;滋养当下生活。    在暄桐教室的六阶…

QT 使用信号和槽,让QLabel的内容实时与QLineEdit同步,类似vue框架的双向绑定

在窗口里放置一个单行文本编辑器&#xff08;QLineEdit&#xff09;和一个标签控件&#xff08;QLabel&#xff09;&#xff0c;实现的效果就是当编辑器的内容被编辑时&#xff0c;标 签控件同步显 示编辑控件里的内容 1&#xff09;当 lineEdit 控件被用户编辑时&#xff0c;它…

边缘密度分布图 | ggExtra包/aplot拼图/ggpubr包 等的实现方法

概述&#xff1a;aplot 拼图效果好 根据网友探索[1]&#xff0c;总结如下&#xff1a; ggExtra 包的拼图间隙有点大&#xff0c;图例在主图和边缘图之间&#xff0c;除非去掉图例&#xff0c;否则没法看。aplot包的默认拼图间隙很小&#xff0c;比较美观&#xff0c;图例在外…

Java——二进制原码、反码和补码

一、简要介绍 原码、反码和补码只是三种二进制不同的表示形式&#xff0c;每个二进制数都有这三个形式。 1、原码 原码是将一个数的符号位和数值位分别表示的方法。 最高位为符号位&#xff0c;0表示正&#xff0c;1表示负&#xff0c;其余位表示数值的绝对值。 例如&…

生成式AI,在云端的绽放与盛开

编辑&#xff1a;阿冒 设计&#xff1a;沐由 毫无疑问&#xff0c;生成式AI已然成为当今技术发展和应用创新的重要引擎之一。 过去的一年多时间里&#xff0c;我们每个人都在目睹和见证着生成式AI是如何以移山倒海的力量&#xff0c;为诸多行业带来革命性乃至颠覆性的变革&…

FS118M 单A口QC协议芯片

FS118M是一个QC快充协议芯片&#xff0c;FS118M可以识别插入的手机类型&#xff0c;选择最为合适的协议应对手机需要。USB Type-A 口的 D连接到FS118M芯片&#xff0c;当手机插入到 USB Type-A 口后&#xff0c;根据各个协议的约定&#xff0c;手机和FS118M之间将开始互相识别&…

计网ppt标黄知识点整理第(2)章节——谢希仁版本、期末复习自用

物理层考虑的是怎样才能在连接各种计算机的传输媒体上传输数据比特流&#xff0c;而不是指具体的传输媒体。4 个特性&#xff1a; 机械特性&#xff1a;指明接口所用接线器的形状和尺寸、引线数目和排列、固定和锁定装置等。 电气特性&#xff1a;指明在接口电缆的各条线上出现…

C/C++开发,2024.x CLion安装,亲测有效

CLion 是一款专为 C 和 C 开发者设计的跨平台集成开发环境&#xff08;IDE&#xff09;&#xff0c;提供了智能代码补全、代码分析、调试和 Git 集成等功能&#xff0c;以提高开发效率和代码质量。 1.下载安装c/c开发工具 CLion 先去官网下载&#xff0c;我这里下载的是最新版…

【docker】docker的安装

如果之前安装了旧版本的docker我们需要进行卸载&#xff1a; 卸载之前的旧版本 卸载 # 卸载旧版本 sudo apt-get remove docker docker-engine docker.io containerd runc # 卸载历史版本 apt-get purge docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker…

问答机器人

怎样做自己的问答机器人&#xff1f; 根据我们提供的数据分析出问题的答案&#xff0c;我们并不需要训练自己的模型 微调模型 finetune&#xff0c;将语言模型调成另外的语言模型&#xff0c;更适合不同类型数据&#xff0c;运用finetune方法将模型变化 知识库模型 embedd…

word避免画质画质模糊方法

问题描述&#xff1a;   近期写文档时会高频率贴图&#xff0c;粘图过程中发现Word会自动压缩图片画质&#xff0c;而且压缩得很严重&#xff0c;下面是一幅图被压缩前后的画质对比 &#xff08;图片压缩前&#xff09; &#xff08;图片压缩后&#xff09; 解决方案&#x…

修改文档日期神器 - Python打造日期修改器

这篇文章将介绍一款使用 Python 开发的实用工具 - 日期修改器。它可以帮助您轻松修改 Word (.docx) 和 PDF 文档的日期信息&#xff0c;满足日常办公和文档整理的需求。 C:\pythoncode\new\modifyfiledate.py 软件功能 支持修改 Word (.docx) 日期信息。允许选择要修改的日期…

leetcode146.LRU缓存,从算法题引入,全面学习LRU和链表哈希表知识

leetcode146. LRU 缓存 题目链接 请你设计并实现一个满足 LRU (最近最少使用) 缓存约束的数据结构。 实现 LRUCache 类&#xff1a; LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存 int get(int key) 如果关键字 key 存在于缓存中&#xff0c;则返回关…

三种字符串的管理方式

NSString的三种实现方式 OC这个语言在不停的升级自己的内存管理&#xff0c;尽量的让自己的 OC的字符串 问题引入 在学习字符串的过程中间会遇到一个因为OC语言更新造成的问题 例如&#xff1a; int main(int argc, const char * argv[]) {autoreleasepool {NSString* str1 …

ZCU102启动镜像(详细版)

ZCU102启动镜像--详细版本 详细步骤1、安装好Vitis&#xff08;GUI界面&#xff09;、 Vivado、 Petalinux软件然后vivado这边的操作就先结束了 创建Petalinux工程编译镜像打包 详细步骤 B站参考视频链接: link 1、安装好Vitis&#xff08;GUI界面&#xff09;、 Vivado、 Pe…

SpringBoot:手动创建应用

Spring提供了在线的Spring Initialzr在线创建Spring Boot项目&#xff0c;为了更好的理解Spring Boot项目&#xff0c;这里我们选择手动创建。 1.新建Web应用 1.1 生成工程 首先要做是创建一个Java项目&#xff0c;这里我们选择使用Maven来支持&#xff0c;使用archetype:ge…

C++进阶之AVL树+模拟实现

目录 目录 一、AVL树的基本概念 1.1 基本概念 二、AVL树的模拟实现 2.1 AVL树节点的定义 2.2 插入操作 2.3 旋转操作 2.4 具体实现 一、AVL树的基本概念 1.1 基本概念 二叉搜索树虽可以缩短查找的效率&#xff0c;但如果数据有序或接近有序二叉搜索树将退化为单支树&…

【论文速读】Self-Rag框架,《Self-Rag: Self-reflective Retrieval augmented Generation》

关于前面的文章阅读《When to Retrieve: Teaching LLMs to Utilize Information Retrieval Effectively》&#xff0c;有网友问与Self-Rag有什么区别。 所以&#xff0c;大概看了一下Self-Rag这篇论文。 两篇文章的方法确实非常像&#xff0c;Self-Rag相对更加复杂一些。 When …

图数据库Neo4j——Neo4j简介、数据结构 Docker版本的部署安装 Cypher语句的入门

前言 MySQL是一种开源的关系型数据库管理系统&#xff0c;使用SQL作为其查询语言&#xff0c;常见的关系型数据库有MySQL、Oracle、SQL Server、PostgreSQL等。相关博客文章如下&#xff1a; 【合集】MySQL的入门进阶强化——从 普通人 到 超级赛亚人 的 华丽转身PostgreSQL数…

计算机系统结构之流水

一、标量流水线的主要性能 吞吐率是流水线单位时间里能流出的任务数或结果数(最大吞吐率&#xff1a;单位时间内计算机所能处理的最多指令条数)。 流水线中经过时间最长的子过程成为瓶颈子过程。最大吞吐率取决于瓶颈段的时间。 实际吞吐率&#xff1a; 加速比&#xff1a; …