文章目录
-
- 概要
- 这个项目是干什么的
- 项目所需储备知识
- 什么是内存池
- 池化技术
- 内存池
- 内存池主要解决的问题
- 框架设计
- 开发计划
- 系统测试情况
- 遇到的主要问题和解决方法
- 分工和协作
- 提交仓库目录和文件描述
- 比赛收获
- 概要
概要
这个项目是干什么的
当前项目是实现一个高并发的内存池,他的原型是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);
}
遇到的主要问题和解决方法
问题
- 多线程环境下的锁竞争问题:多个线程同时访问内存池时,如何减少锁竞争以提高性能。
- 平台及兼容性在不同操作系统和架构下,内存分配和管理的差异可能导致兼容性问题。
- 内存池自身数据结构的管理:内存池自身数据结构(如SpanList中的span等)的管理也可能使用到malloc,没有完全脱离malloc。
解决办法
- 减少锁竞争:
- 通过为每个线程分配独立的threadCache,减少多线程环境下的锁竞争。
- 在centralCache中使用桶锁(bucket lock)等技术,进一步减少锁竞争。
- 平台及兼容性处理:
- 根据不同平台和架构的特性,选择合适的内存分配和管理策略。
- 在Linux等系统下,将某些特定的内存分配函数(如VirtualAlloc)替换为brk等。
- 内存池自身数据结构的管理:
- 对于内存池自身数据结构的管理,尽量减少使用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. 拓展了技术视野和知识面
- 在设计高并发内存池的过程中,我接触了许多新的技术和工具,如高性能数据结构、并发编程库、性能测试工具等。这些新的技术和工具拓展了我的技术视野和知识面,使我对计算机系统的底层原理和性能优化有了更深入的理解。
总之,参加这次比赛是一次非常有价值的经历。通过这次经历,我不仅提升了自己的技术能力和团队协作能力,还拓展了自己的技术视野和知识面。我相信这些收获将对我未来的职业发展产生积极的影响。