为了使多个线程并发地分配和释放内存,必须在分配器方法中添加互斥锁。

全局内存管理器(通过new()和delete()实现)是通用的,因此它的开销也非常大。

因为单线程内存管理器要比多线程内存管理器快的多,所以如果要分配的大多数内存块限于单线程中使用,那么可以显著提升性能。

如果开发了一套有效的单线程分配器,那么通过模板可以方便地将它们扩展到多线程环境中。

以下是测试代码(multi_threaded_memory_pool.cpp):

  1. #include "multi_threaded_memory_pool.hpp"
  2. #include <iostream>
  3. #include <chrono>
  4. #include <string>
  5. #include <mutex>
  6. namespace multi_threaded_memory_pool_ {
  7. // reference: 《提高C++性能的编程技术》:第七章:多线程内存池
  8. class Rational1 {
  9. public:
  10. Rational1(int a = 0, int b = 1) : n(a), d(b) {}
  11. private:
  12. int n; // 分子
  13. int d; // 分母
  14. };
  15. // 单线程可变大小内存管理器:
  16. // MemoryChunk类取代之前版本中使用的NextOnFreeList类,它用来把不同大小的内存块连接起来形成块序列
  17. class MemoryChunk {
  18. public:
  19. MemoryChunk(MemoryChunk* nextChunk, size_t chunkSize);
  20. // 析构函数释放构造函数获得的内存空间
  21. ~MemoryChunk() { delete [] mem; }
  22. inline void* alloc(size_t size);
  23. inline void free(void* someElement);
  24. // 指向列表下一内存块的指针
  25. MemoryChunk* nextMemChunk() { return next; }
  26. // 当前内存块剩余空间大小
  27. size_t spaceAvailable() { return chunkSize - bytesAlreadyAllocated; }
  28. // 这是一个内存块的默认大小
  29. enum { DEFAULT_CHUNK_SIZE = 4096 };
  30. private:
  31. MemoryChunk* next;
  32. void* mem;
  33. // 一个内存块的默认大小
  34. size_t chunkSize;
  35. // 当前内存块中已分配的字节数
  36. size_t bytesAlreadyAllocated;
  37. };
  38. // 构造函数首先确定内存块的适当大小,然后根据这个大小从堆上分配私有存储空间
  39. // MemoryChunk将next成员指向输入参数nextChunk, nextChunk是列表先前的头部
  40. MemoryChunk::MemoryChunk(MemoryChunk* nextChunk, size_t reqSize)
  41. {
  42. chunkSize = (reqSize > DEFAULT_CHUNK_SIZE) ? reqSize : DEFAULT_CHUNK_SIZE;
  43. next = nextChunk;
  44. bytesAlreadyAllocated = 0;
  45. mem = new char[chunkSize];
  46. }
  47. // alloc函数处理内存分配请求,它返回一个指针,该指针指向mem所指向的MemoryChunk私有存储空间中的可用空间。
  48. // 该函数通过更新该块中已分配的字节数来记录可用空间的大小
  49. void* MemoryChunk::alloc(size_t requestSize)
  50. {
  51. void* addr = static_cast<void*>(static_cast<char*>(mem) + bytesAlreadyAllocated);
  52. bytesAlreadyAllocated += requestSize;
  53. return addr;
  54. }
  55. // 在该实现中,不用担心空闲内存段的释放。当对象被删除后,整个内存块将被释放并且返回到堆上
  56. inline void MemoryChunk::free(void* doomed)
  57. {
  58. }
  59. // MemoryChunk只是一个辅助类,ByteMemoryPoll类用它来实现可变大小的内存管理
  60. class ByteMemoryPool {
  61. public:
  62. ByteMemoryPool(size_t initSize = MemoryChunk::DEFAULT_CHUNK_SIZE);
  63. ~ByteMemoryPool();
  64. // 从私有内存池分配内存
  65. inline void* alloc(size_t size);
  66. // 释放先前从内存池中分配的内存
  67. inline void free(void* someElement);
  68. private:
  69. // 内存块列表,它是我们的私有存储空间
  70. MemoryChunk* listOfMemoryChunks = nullptr;
  71. // 向我们的私有存储空间添加一个内存块
  72. void expandStorage(size_t reqSize);
  73. };
  74. // 虽然内存块列表可能包含多个块,但只有第一块拥有可用于分配的内存。其它块表示已分配的内存。
  75. // 列表的首个元素是唯一能够分配可以内存的块。
  76. // 构造函数接收initSize参数来设定一个内存块的大小,即构造函数借此来设置单个内存块的大小。
  77. // expandStorage方法使listOfMemoryChunks指向一个已分配的MemoryChunk对象
  78. // 创建ByteMemoryPool对象,生成私有存储空间
  79. ByteMemoryPool::ByteMemoryPool(size_t initSize)
  80. {
  81. expandStorage(initSize);
  82. }
  83. // 析构函数遍历内存块列表并且删除它们
  84. ByteMemoryPool::~ByteMemoryPool()
  85. {
  86. MemoryChunk* memChunk = listOfMemoryChunks;
  87. while (memChunk) {
  88. listOfMemoryChunks = memChunk->nextMemChunk();
  89. delete memChunk;
  90. memChunk = listOfMemoryChunks;
  91. }
  92. }
  93. // alloc函数确保有足够的可用空间,而把分配任务托付给列表头的MemoryChunk
  94. void* ByteMemoryPool::alloc(size_t requestSize)
  95. {
  96. size_t space = listOfMemoryChunks->spaceAvailable();
  97. if (space < requestSize) {
  98. expandStorage(requestSize);
  99. }
  100. return listOfMemoryChunks->alloc(requestSize);
  101. }
  102. // 释放之前分配的内存的任务被委派给列表头部的MemoryChunk来完成
  103. // MemoryChunk::free不做任何事情,因为ByteMemoryPool的实现不会重用之前分配的内存。如果需要更多内存,
  104. // 我们将创建新的内存块以便今后分配使用。在内存池被销毁时,内存释放回堆中。ByteMemoryPool析构函数
  105. // 释放所有的内存块到堆中
  106. inline void ByteMemoryPool::free(void* doomed)
  107. {
  108. listOfMemoryChunks->free(doomed);
  109. }
  110. // 若遇到内存块用尽这种不太可能的情况,我们通过创建新的内存块并把它添加到内存块列表的头部来扩展它
  111. void ByteMemoryPool::expandStorage(size_t reqSize)
  112. {
  113. listOfMemoryChunks = new MemoryChunk(listOfMemoryChunks, reqSize);
  114. }
  115. // 多线程内存池实现
  116. template<class POOLTYPE, class LOCK>
  117. class MTMemoryPool {
  118. public:
  119. // 从freeList里分配一个元素
  120. inline void* alloc(size_t size);
  121. // 返回一个元素给freeList
  122. inline void free(void* someElement);
  123. private:
  124. POOLTYPE stPool; // 单线程池
  125. LOCK theLock;
  126. };
  127. // alloc方法将分配任务委托给内存池成员,而将锁定任务委托给锁成员
  128. template<class M, class L>
  129. inline void* MTMemoryPool<M, L>::alloc(size_t size)
  130. {
  131. void* mem;
  132. theLock.lock();
  133. mem = stPool.alloc(size);
  134. theLock.unlock();
  135. return mem;
  136. }
  137. template<class M, class L>
  138. inline void MTMemoryPool<M, L>::free(void* doomed)
  139. {
  140. theLock.lock();
  141. stPool.free(doomed);
  142. theLock.unlock();
  143. }
  144. class ABCLock { // 抽象基类
  145. public:
  146. virtual ~ABCLock() {}
  147. virtual void lock() = 0;
  148. virtual void unlock() = 0;
  149. };
  150. class MutexLock : public ABCLock {
  151. public:
  152. MutexLock() {}
  153. ~MutexLock() {}
  154. inline void lock() { mtx.lock(); }
  155. inline void unlock() { mtx.unlock(); }
  156. private:
  157. std::mutex mtx;
  158. };
  159. class Rational2 {
  160. public:
  161. Rational2(int a = 0, int b = 1) : n(a),d(b) {}
  162. void* operator new(size_t size) { return memPool->alloc(size); }
  163. void operator delete(void* doomed, size_t size) { memPool->free(doomed); }
  164. static void newMemPool() { memPool = new MTMemoryPool<ByteMemoryPool, MutexLock>; }
  165. static void deleteMemPool() { delete memPool; }
  166. private:
  167. int n; // 分子
  168. int d; // 分母
  169. static MTMemoryPool<ByteMemoryPool, MutexLock>* memPool;
  170. };
  171. MTMemoryPool<ByteMemoryPool, MutexLock>* Rational2::memPool = nullptr;
  172. ///
  173. // 多线程内存池实现应用在单线程环境中
  174. class DummyLock : public ABCLock {
  175. public:
  176. inline void lock() {}
  177. inline void unlock() {}
  178. };
  179. class Rational3 {
  180. public:
  181. Rational3(int a = 0, int b = 1) : n(a),d(b) {}
  182. void* operator new(size_t size) { return memPool->alloc(size); }
  183. void operator delete(void* doomed, size_t size) { memPool->free(doomed); }
  184. static void newMemPool() { memPool = new MTMemoryPool<ByteMemoryPool, DummyLock>; }
  185. static void deleteMemPool() { delete memPool; }
  186. private:
  187. int n; // 分子
  188. int d; // 分母
  189. static MTMemoryPool<ByteMemoryPool, DummyLock>* memPool;
  190. };
  191. MTMemoryPool<ByteMemoryPool, DummyLock>* Rational3::memPool = nullptr;
  192. int test_multi_threaded_memory_pool_1()
  193. {
  194. using namespace std::chrono;
  195. high_resolution_clock::time_point time_start, time_end;
  196. const int cycle_number1{10000}, cycle_number2{1000};
  197. { // 测试全局函数new()和delete()的基准性能
  198. Rational1* array[cycle_number2];
  199. time_start = high_resolution_clock::now();
  200. for (int j =0; j < cycle_number1; ++j) {
  201. for (int i =0; i < cycle_number2; ++i) {
  202. array[i] = new Rational1(i);
  203. }
  204. for (int i = 0; i < cycle_number2; ++i) {
  205. delete array[i];
  206. }
  207. }
  208. time_end = high_resolution_clock::now();
  209. fprintf(stdout, "global function new/delete time spent: %f seconds\n",(duration_cast<duration<double>>(time_end - time_start)).count());
  210. }
  211. { // 多线程内存池测试代码
  212. Rational2* array[cycle_number2];
  213. time_start = high_resolution_clock::now();
  214. Rational2::newMemPool();
  215. for (int j = 0; j < cycle_number1; ++j) {
  216. for (int i = 0; i < cycle_number2; ++i) {
  217. array[i] = new Rational2(i);
  218. }
  219. for (int i = 0; i < cycle_number2; ++i) {
  220. delete array[i];
  221. }
  222. }
  223. Rational2::deleteMemPool();
  224. time_end = high_resolution_clock::now();
  225. fprintf(stdout, "multi-threaded variable-size memory manager time spent: %f seconds\n",(duration_cast<duration<double>>(time_end - time_start)).count());
  226. }
  227. { // 多线程内存池应用在单线程环境下测试代码
  228. Rational3* array[cycle_number2];
  229. time_start = high_resolution_clock::now();
  230. Rational3::newMemPool();
  231. for (int j = 0; j < cycle_number1; ++j) {
  232. for (int i = 0; i < cycle_number2; ++i) {
  233. array[i] = new Rational3(i);
  234. }
  235. for (int i = 0; i < cycle_number2; ++i) {
  236. delete array[i];
  237. }
  238. }
  239. Rational3::deleteMemPool();
  240. time_end = high_resolution_clock::now();
  241. fprintf(stdout, "multi-threaded variable-size memory manager in single-threaded environment time spent: %f seconds\n",(duration_cast<duration<double>>(time_end - time_start)).count());
  242. }
  243. return 0;
  244. }
  245. } // namespace multi_threaded_memory_pool_

执行结果如下:

GitHub:https://github.com/fengbingchun/Messy_Test

转载自:https://blog.csdn.net/fengbingchun/article/details/84592548

Multi-thread--提高C++性能的编程技术笔记:多线程内存池+测试代码相关推荐

  1. 提高C++性能的编程技术笔记:编码优化+测试代码

    缓存:在现代处理器中,缓存经常与处理器中的数据缓存和指令缓存联系在一起.缓存主要用来存储使用频繁而且代价高昂的计算结果,这样就可以避免对这些结果的重复计算.如,循环内对常量表达式求值是一种常见的低性能 ...

  2. 提高C++性能的编程技术笔记:引用计数+测试代码

    引用计数(reference counting):基本思想是将销毁对象的职责从客户端代码转移到对象本身.对象跟踪记录自身当前被引用的数目,在引用计数达到零时自行销毁.换句话说,对象不再被使用时自行销毁 ...

  3. 提高C++性能的编程技术笔记:内联+测试代码

    内联类似于宏,在调用方法内部展开被调用方法,以此来代替方法的调用.一般来说表达内联意图的方式有两种:一种是在定义方法时添加内联保留字的前缀:另一种是在类的头部声明中定义方法. 虽然内联方法的调用方式和 ...

  4. 提高C++性能的编程技术笔记:临时对象+测试代码

    类型不匹配:一般情况是指当需要X类型的对象时提供的却是其它类型的对象.编译器需要以某种方式将提供的类型转换成要求的X类型.这一过程可能会产生临时对象. 按值传递:创建和销毁临时对象的代价是比较高的.倘 ...

  5. 提高C++性能的编程技术笔记:跟踪实例+测试代码

    当提高性能时,我们必须记住以下几点: (1). 内存不是无限大的.虚拟内存系统使得内存看起来是无限的,而事实上并非如此. (2). 内存访问开销不是均衡的.对缓存.主内存和磁盘的访问开销不在同一个数量 ...

  6. 提高C++性能的编程技术笔记:多线程内存池+测试代码

    为了使多个线程并发地分配和释放内存,必须在分配器方法中添加互斥锁. 全局内存管理器(通过new()和delete()实现)是通用的,因此它的开销也非常大. 因为单线程内存管理器要比多线程内存管理器快的 ...

  7. 提高C++性能的编程技术笔记:总结

    <提高C++性能的编程技术>这本书是2011年出版的,书中有些内容的介绍可能已经过时,已不再适用于现在的C++编程中,但大部分内容还是很有参考意义的. 这里是基于之前所有笔记的简单总结,笔 ...

  8. 提高C++性能的编程技术笔记:设计优化/可扩展性/系统体系结构相关+测试代码

    1. 设计优化 我们可以粗略地将性能优化分为两种类型:编码优化和设计优化.编码优化定义为不需要完整理解要解决的问题或者应用程序的执行流程就能实施的优化.通过定义看出,编码优化用于局部代码,同时该过程不 ...

  9. 提高C++性能的编程技术笔记:标准模板库+测试代码

    标准模板库(Standard Template Library, STL)是容器和通用算法的强效组合. 渐近复杂度:算法的渐近复杂度是对算法性能的近似估计.它是算法集到特定性能标准集的映射.如果需要对 ...

最新文章

  1. KMP算法的详细解释及实现
  2. 梯度下降法——得到的结果可能是局部最优值,如果凸函数则可保证梯度下降得到的是全局最优值...
  3. python安装步骤电脑版-超详细的小白python3.X安装教程|Python安装
  4. linux update语句,MySQL 多表 update sql语句总结
  5. arcgis怎么用python重新排序_python - 根据排序数据添加新的顺序ID号(ArcGIS) - 堆栈内存溢出...
  6. 最优化学习笔记(十)——对偶线性规划
  7. 汉诺塔VII(递推,模拟)
  8. cs文件,外部类操作窗体控件
  9. mysql jdbc 驱动名称_【经验分享】常用 JDBC 驱动名字和 URL 列表
  10. linux 性能检测工具之 dstat
  11. 数据结构上机实践第二周项目1
  12. android如何展示富文本_Android中如何在textView实现富文本
  13. 橙子减肥法:好吃快速成为瘦美人 - 健康程序员,至尚生活!
  14. 微型计算机系统性能优化及测试,第八章 微型计算机系统的测试、优化和升级.doc...
  15. JS中every()和some()的对比使用丨蓄力计划
  16. 灵魂画手教你浅拷贝与深拷贝
  17. 头哥 (Educoder)数据结构与算法实验:实验11 TYJ动态规划
  18. 大数据学习:大数据就业前景和就业方向
  19. 如何回复客户英文邮件
  20. 削华为足,以适IBM之履的再造流程

热门文章

  1. 容器编排技术 -- Kubernetes kubectl 与 Docker 命令关系
  2. jQuery操作CSS常见问题
  3. 亮度 调整_摄影后期必备!如何用亮度蒙版技术精准调整照片影调
  4. java 课后习题 判断用户输入的数是否为质数
  5. Spring框架声明式事务管理
  6. 使用^、(异或、并且)位运算 实现算数加法(+)
  7. vue :key的说明 看到这文章,解决你的疑问
  8. 【Java】计算符号函数的值
  9. C#LeetCode刷题之#747-至少是其他数字两倍的最大数( Largest Number At Least Twice of Others)
  10. C#算法设计查找篇之01-顺序查找