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

2024-06-02 23:04

本文主要是介绍从零开始实现一个可靠、健壮的内存池,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

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

概要

这个项目是干什么的

当前项目是实现一个高并发的内存池,他的原型是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.chinasem.cn/article/1025336

相关文章

HTML5 getUserMedia API网页录音实现指南示例小结

《HTML5getUserMediaAPI网页录音实现指南示例小结》本教程将指导你如何利用这一API,结合WebAudioAPI,实现网页录音功能,从获取音频流到处理和保存录音,整个过程将逐步... 目录1. html5 getUserMedia API简介1.1 API概念与历史1.2 功能与优势1.3

Java实现删除文件中的指定内容

《Java实现删除文件中的指定内容》在日常开发中,经常需要对文本文件进行批量处理,其中,删除文件中指定内容是最常见的需求之一,下面我们就来看看如何使用java实现删除文件中的指定内容吧... 目录1. 项目背景详细介绍2. 项目需求详细介绍2.1 功能需求2.2 非功能需求3. 相关技术详细介绍3.1 Ja

使用Python和OpenCV库实现实时颜色识别系统

《使用Python和OpenCV库实现实时颜色识别系统》:本文主要介绍使用Python和OpenCV库实现的实时颜色识别系统,这个系统能够通过摄像头捕捉视频流,并在视频中指定区域内识别主要颜色(红... 目录一、引言二、系统概述三、代码解析1. 导入库2. 颜色识别函数3. 主程序循环四、HSV色彩空间详解

PostgreSQL中MVCC 机制的实现

《PostgreSQL中MVCC机制的实现》本文主要介绍了PostgreSQL中MVCC机制的实现,通过多版本数据存储、快照隔离和事务ID管理实现高并发读写,具有一定的参考价值,感兴趣的可以了解一下... 目录一 MVCC 基本原理python1.1 MVCC 核心概念1.2 与传统锁机制对比二 Postg

SpringBoot整合Flowable实现工作流的详细流程

《SpringBoot整合Flowable实现工作流的详细流程》Flowable是一个使用Java编写的轻量级业务流程引擎,Flowable流程引擎可用于部署BPMN2.0流程定义,创建这些流程定义的... 目录1、流程引擎介绍2、创建项目3、画流程图4、开发接口4.1 Java 类梳理4.2 查看流程图4

C++中零拷贝的多种实现方式

《C++中零拷贝的多种实现方式》本文主要介绍了C++中零拷贝的实现示例,旨在在减少数据在内存中的不必要复制,从而提高程序性能、降低内存使用并减少CPU消耗,零拷贝技术通过多种方式实现,下面就来了解一下... 目录一、C++中零拷贝技术的核心概念二、std::string_view 简介三、std::stri

C++高效内存池实现减少动态分配开销的解决方案

《C++高效内存池实现减少动态分配开销的解决方案》C++动态内存分配存在系统调用开销、碎片化和锁竞争等性能问题,内存池通过预分配、分块管理和缓存复用解决这些问题,下面就来了解一下... 目录一、C++内存分配的性能挑战二、内存池技术的核心原理三、主流内存池实现:TCMalloc与Jemalloc1. TCM

OpenCV实现实时颜色检测的示例

《OpenCV实现实时颜色检测的示例》本文主要介绍了OpenCV实现实时颜色检测的示例,通过HSV色彩空间转换和色调范围判断实现红黄绿蓝颜色检测,包含视频捕捉、区域标记、颜色分析等功能,具有一定的参考... 目录一、引言二、系统概述三、代码解析1. 导入库2. 颜色识别函数3. 主程序循环四、HSV色彩空间

Python实现精准提取 PDF中的文本,表格与图片

《Python实现精准提取PDF中的文本,表格与图片》在实际的系统开发中,处理PDF文件不仅限于读取整页文本,还有提取文档中的表格数据,图片或特定区域的内容,下面我们来看看如何使用Python实... 目录安装 python 库提取 PDF 文本内容:获取整页文本与指定区域内容获取页面上的所有文本内容获取

基于Python实现一个Windows Tree命令工具

《基于Python实现一个WindowsTree命令工具》今天想要在Windows平台的CMD命令终端窗口中使用像Linux下的tree命令,打印一下目录结构层级树,然而还真有tree命令,但是发现... 目录引言实现代码使用说明可用选项示例用法功能特点添加到环境变量方法一:创建批处理文件并添加到PATH1