定制软件开发内存池组件以及根据nginx内存池源码设计实现简易内存池

目录


 定制软件开发造轮子内存池原因引入 

作为C/C++程序员, 相较JAVA定制软件开发程序员的一个重大特征定制软件开发是我们可以直接访问内存, 定制软件开发自己管理内存, 定制软件开发这个可以说是我们的特色, 定制软件开发也是我们的苦楚了.

java定制软件开发可以有虚拟机帮助管理内存, 定制软件开发但是我们只能自己管理内存, 定制软件开发一不小心产生了内存泄漏问题, 定制软件开发又特别是服务器的内存泄漏问题, 定制软件开发进程不死去, 定制软件开发泄漏的内存就一直无法回收.

定制软件开发所以对于内存的管理一直是我们C定制软件开发系列程序员深挖的事情. 

所以对于C++定制软件开发有智能指针这个东西. 定制软件开发还有内存池组件. 定制软件开发内存池组件也不能完全定制软件开发避免内存泄漏, 定制软件开发但是它可以很好的帮助定制软件开发我们定位内存泄漏的点, 定制软件开发以及可以减少内存申请定制软件开发和释放的次数, 提高效率

大量的malloc/free定制软件开发小内存所带来的弊端

弊端

  1. malloc/free定制软件开发的底层是调用系统调用, 这两者库函数是对于系统调用的封装, 频繁的系统调用所带来的用户内核态切换花费大量时间, 大大降低系统执行效率
  2. 频繁的申请小内存, 带来的大量内存碎片, 内存使用率低下且导致无法申请大块的内存
  3. 没有内存回收机制, 很容易造成内存泄漏

内存碎片出现原因解释

  • 内部内存碎片定义:  已经被分配出去了(明确分配到一个进程), 但是无法被利用的空间 
  • 内存分配的起始地址 一定要是 4, 8, 16整除地址
  • 内存是按照页进行分配的, 中间会产生外部内存碎片, 无法分配给进程
  • 内部内存碎片:频繁的申请小块内存导致了内存不连续性,中间的小内存间隙又不足以满足我们的内存申请要求, 无法申请出去利用起来, 这个就是内部内存碎片.

出现场景

最为典型的场景就是高并发是的频繁内存申请, 释放. (http请求) (tcp连接)

大牛解决措施(nginx内存池)  

nginx内存池, 公认的设计方式非常巧妙的一款内存池设计组件, 专门针对高并发下面的大量的内存申请释放而产生的. 

在系统层,我们可以使用高性能内存管理组件 Tcmalloc Jemalloc(优化效率和碎片问题)

在应用层: 我们可以根据需求设计内存池进行管理  (高并发可以借助nginx内存池设计)

内存池技术

啥叫作内存池技术

就是说在真正使用内存之前, 先提前申请分配一定数量的、大小相等(一般情况下)的内存块留作备用, 当需要分配内存的时候, 直接从内存块中获取. 如果内存块不够了, 再申请新的内存块.

内存池: 就是将这些提前申请的内存块组织管理起来的数据结构

优势何在:统一对程序所使用的内存进行统一的分配和回收, 提前申请的块, 然后将块中的内存合理的分配出去, 极大的减少了系统调用的次数. 提高了内存利用率.  统一的内存分配回收使得内存泄漏出现的概率大大降低

内存池技术为啥可以解决上文弊端

高并发时系统调用频繁(malloc free频繁),降低了系统的执行效率

  • 内存池提前预先分配大块内存,统一释放,极大的减少了malloc 和 free 等函数的调用。

频繁使用时增加了系统内存的碎片,降低内存使用效率

  • 内存池每次请求分配大小适度的内存块,最大避免了碎片的产生

没有内存回收机制,容易造成内存泄漏

  • 在生命周期结束后统一释放内存,极大的避免了内存泄露的发生

高并发内存池nginx内存池源码刨析

啥是高并发

系统能够同时并行处理很多请求就是高并发

高并发具备的特征

  • 响应时间短
  • 支持并发用户数高
  • 支持用户接入量高
  • 连接建立时间短

nginx_memory_pool为啥就适合高并发

内存池生存时间应该尽可能短,与请求或者连接具有相同的周期

减少碎片堆积和内存泄漏

避免不同请求连接之间互相影响

一个连接或者一个请求就创建一个内存池专门为其服务, 内存池的生命周期和连接的生命周期保持一致. 

仿写nginx内存池

实现思路

  • 对于每个请求或者连接都会建立相应的内存池,建立好内存池之后,我们可以直接从内存池中申请所需要的内存,不用去管内存的释放,当内存池使用完成之后一次性销毁内存池。
  • 区分大小内存块的申请和释放,大于内存池块最大尺寸的定义为大内存块,使用单独的大内存块链表保存,即时分配和释放
  • 小于等于池尺寸的定义为小内存块,直接从预先分配的内存块中提取,不够就扩充池中的内存,在生命周期内对小块内存不做释放,直到最后统一销毁。

 内存池大小, 以及内存对齐的宏定义

  1. #define MP_ALIGNMENT 32
  2. #define MP_PAGE_SIZE 4096
  3. #define MP_MAX_ALLOC_FROM_POOL (MP_PAGE_SIZE-1)
  4. #define mp_align_ptr(p, alignment) (void *)((((size_t)p)+(alignment-1)) & ~(alignment-1))
  5. //分配内存起点对齐

结构定义以及图解分析

  1. typedef struct mp_large_s {
  2. struct mp_large_s* next;
  3. void* alloc;//data区
  4. } mp_large_s;
  5. typedef struct mp_node_s {
  6. unsigned char* last;//下一次内存分配的起点
  7. unsigned char* end;//当前内存块末尾
  8. size_t failed;//当前内存块分配失败的次数
  9. struct mp_node_s* next;
  10. } mp_node_s;
  11. typedef struct mp_pool_s {
  12. mp_large_s* large;//指向大块内存起点
  13. mp_node_s* current;//指向当前可分配的小内存块起点
  14. int max;//小块最大内存
  15. mp_node_s head[0];//存储地址, 不占据内存,变长结构体技巧
  16. //存储首块小内存块head地址
  17. } mp_pool_s;

mp_pool_s     内存池结构

  1. large       指向第一个大块
  2. current   指向当前可分配的小块
  3. head       始终指向第一块小块

mp_node_s     小块内存结构

  1. last         下一次内存分配的起点, 本次内存分配的终点
  2. end         块内存末尾
  3. failed      当前内存块申请内存的失败次数, nginx采取的方式是失败次数达到一定程度就更换current,current是开始尝试分配的内存块, 也就是说失败达到一定次数, 就不再申请这个内存块了.

mp_large_s        大块内存块

  1. 正常的申请, 然后使用链表连接管理起来.
  2. alloc           内存块, 分配内存块 

函数原型以及功能叙述

  1. //函数申明
  2. mp_pool_s *mp_create_pool(size_t size);//创建内存池
  3. void mp_destory_pool( mp_pool_s *pool);//销毁内存池
  4. void *mp_alloc(mp_pool_s *pool, size_t size);
  5. //从内存池中申请并且进行字节对齐
  6. void *mp_nalloc(mp_pool_s *pool, size_t size);
  7. //从内存池中申请不进行字节对齐
  8. void *mp_calloc(mp_pool_s *pool, size_t size);
  9. //模拟calloc
  10. void mp_free(mp_pool_s *pool, void *p);
  11. void mp_reset_pool(struct mp_pool_s *pool);
  12. //重置内存池
  13. static void *mp_alloc_block(struct mp_pool_s *pool, size_t size);
  14. //申请小块内存
  15. static void *mp_alloc_large(struct mp_pool_s *pool, size_t size);
  16. //申请大块内存

对应nginx函数原型

重点函数分块细节刨析

mp_create_pool: 创建线程池

第一块内存: 大小设置为  size + sizeof(node) + sizeof(pool) ?

mp_node_s head[0] 啥意思?

  1. mp_pool_s* mp_create_pool(size_t size) {
  2. struct mp_pool_s *p = NULL;
  3. int ret = posix_memalign((void **)&p, MP_ALIGNMENT, size + sizeof(mp_pool_s) + sizeof(mp_node_s));
  4. if (ret) {
  5. return NULL;
  6. }
  7. //内存池小块的大小限制
  8. p->max = (size < MP_MAX_ALLOC_FROM_POOL) ? size : MP_MAX_ALLOC_FROM_POOL;
  9. p->current = p->head;//第一块为当前块
  10. p->large = NULL;
  11. p->head->last = (unsigned char *)p + sizeof( mp_pool_s) + sizeof(mp_node_s);
  12. p->head->end = p->head->last + size;
  13. p->head->failed = 0;
  14. return p;
  15. }

看完了代码来回答一下问题

  1. 为了尽可能地避免内存碎片地产生, 小内存地申请, 于是我采取地方式是将 memory pool内存池也放入到首块内存中地方式. 同时所有地node结点信息也都统一存储在每一个内存块中.
  2. head[0] : 是一种常用于变长结构体地技巧, 不占用内存, 仅仅只是表示一个地址信息, 存储head node 的地址. 

mp_alloc 带字节对齐的内存申请

首先按照size大小选择内存分配方式, 小于等于线程池小块最大大小限制就从已有小块中申请, 小块不足就调用mp_alloc_block创建新的小块   否则就调用 mp_alloc_large 申请创建一个大块内存

mp_align_ptr 用于字节对齐

  1. void *mp_alloc(mp_pool_s *pool, size_t size) {
  2. mp_node_s* p = NULL;
  3. unsigned char* m = NULL;
  4. if (size <= MP_MAX_ALLOC_FROM_POOL) {//从小块中分配
  5. p = pool->current;
  6. do {//循环尝试从现有小块中申请
  7. m = mp_align_ptr(p->last, MP_ALIGNMENT);
  8. if ((size_t)(p->end - m) >= size) {
  9. p->last = m + size;
  10. return m;
  11. }
  12. p = p->next;
  13. } while (p);
  14. //说明小块中都分配失败了, 于是从新申请一个小块
  15. return mp_alloc_block(pool, size);
  16. }
  17. //从大块中分配
  18. return mp_alloc_large(pool, size);
  19. }

mp_alloc_block 申请创建新的小块内存

psize 大小等于mp_node_s结点内存大小 +  实际可用内存块大小

搞清楚内存块组成:结点信息 + 实际可用内存块

返回的内存是实际可用内存的起始地址

  1. //申请小块内存
  2. void *mp_alloc_block(struct mp_pool_s *pool, size_t size) {
  3. unsigned char* m = NULL;
  4. size_t psize = 0;//内存池每一块的大小
  5. psize = (size_t)((unsigned char*)pool->head->end - (unsigned char*)pool->head);
  6. int ret = posix_memalign((void**)&m, MP_ALIGNMENT, psize);
  7. if (ret) return NULL;
  8. //此时已经分配出来一个新的块了
  9. mp_node_s* new_node, *p, *current;
  10. new_node = (mp_node_s*)m;
  11. new_node->end = m + psize;
  12. new_node->failed = 0;
  13. new_node->next = NULL;
  14. m += sizeof(mp_node_s);//跳过node
  15. //对于m进行地址起点内存对齐
  16. m = mp_align_ptr(m, MP_ALIGNMENT);
  17. new_node->last = m + size;
  18. current = pool->current;
  19. //循环寻找新的可分配内存块起点current
  20. for (p = current; p->next; p = p->next) {
  21. if (p->failed++ > 4) {
  22. current = p->next;
  23. }
  24. }
  25. //将new_node连接到最后一块内存上, 并且尝试跟新pool->current
  26. pool->current = current ? current : new_node;
  27. p->next = new_node;
  28. return m;
  29. }

mp_alloc_large 申请创建新的大块内存

大块内存参考nginx_pool 采取采取的是malloc分配

先分配出来所需大块内存. 在pool的large链表中寻找是否存在空闲的alloc. 存在则将内存挂在上面返回.  寻找5次还没有找到就另外申请一个新的large结点挂载内存, 链接到large list中管理

mp_large_s* node 是从内存池中分配的, 也就是从小块中分配的 why? 减少内存碎片, 将大块的node信息放入小块内存中,避免小内存的申请, 减少内存碎片

留疑? 空闲的alloc从何而来?

  1. void *mp_alloc_large(struct mp_pool_s *pool, size_t size) {
  2. void* p = malloc(size);
  3. if (p == NULL) return NULL;
  4. mp_large_s* l = NULL;
  5. size_t cnt = 0;
  6. for (l = pool->large; l; l = l->next) {
  7. if (l->alloc) {
  8. l->alloc = p;
  9. return p;
  10. }
  11. if (cnt++ > 3) {
  12. break;//为了提高效率, 检查前5个块, 没有空闲alloc就从新申请large
  13. }
  14. }
  15. l = mp_alloc(pool, sizeof(struct mp_large_s));
  16. if (l == NULL) {
  17. free(p);
  18. return NULL;
  19. }
  20. l->alloc = p;
  21. l->next = pool->large;
  22. pool->large = l;
  23. return p;
  24. }

空闲的alloc是被free掉了空闲出来的.   虽然nginx采取的是小块不单独回收, 最后统一回收, 因为小块的回收非常难以控制, 不清楚何时可以回收. 但是对于大块nginx提供了free回收接口. 

 mp_free_large 回收大块内存资源

  1. void mp_free_large(mp_pool_s *pool, void *p) {
  2. mp_large_s* l = NULL;
  3. for (l = pool->large; l; l = l->next) {
  4. if (p == l->alloc) {
  5. free(l->alloc);
  6. l->alloc = NULL;
  7. return ;
  8. }
  9. }
  10. }

整体代码附下

  1. #ifndef _MPOOL_H_
  2. #define _MPOOL_H_
  3. #include <stdlib.h>
  4. #include <stdio.h>
  5. #include <string.h>
  6. #include <unistd.h>
  7. #include <fcntl.h>
  8. #define MP_ALIGNMENT 32
  9. #define MP_PAGE_SIZE 4096
  10. #define MP_MAX_ALLOC_FROM_POOL (MP_PAGE_SIZE-1)
  11. #define mp_align_ptr(p, alignment) (void *)((((size_t)p)+(alignment-1)) & ~(alignment-1))
  12. //内存起点对齐
  13. typedef struct mp_large_s {
  14. struct mp_large_s* next;
  15. void* alloc;//data区
  16. } mp_large_s;
  17. typedef struct mp_node_s {
  18. unsigned char* last;//下一次内存分配的起点
  19. unsigned char* end;//当前内存块末尾
  20. size_t failed;//当前内存块分配失败的次数
  21. struct mp_node_s* next;
  22. } mp_node_s;
  23. typedef struct mp_pool_s {
  24. mp_large_s* large;//指向大块内存起点
  25. mp_node_s* current;//指向当前可分配的小内存块起点
  26. int max;//小块最大内存
  27. mp_node_s head[0];//存储地址, 不占据内存,变长结构体技巧
  28. //存储首块小内存块head地址
  29. } mp_pool_s;
  30. //函数申明
  31. mp_pool_s *mp_create_pool(size_t size);//创建内存池
  32. void mp_destory_pool( mp_pool_s *pool);//销毁内存池
  33. void *mp_alloc(mp_pool_s *pool, size_t size);
  34. //从内存池中申请并且进行字节对齐
  35. void *mp_nalloc(mp_pool_s *pool, size_t size);
  36. //从内存池中申请不进行字节对齐
  37. void *mp_calloc(mp_pool_s *pool, size_t size);
  38. //模拟calloc
  39. void mp_free(mp_pool_s *pool, void *p);
  40. void mp_reset_pool(struct mp_pool_s *pool);
  41. //重置内存池
  42. static void *mp_alloc_block(struct mp_pool_s *pool, size_t size);
  43. //申请小块内存
  44. static void *mp_alloc_large(struct mp_pool_s *pool, size_t size);
  45. //申请大块内存
  46. mp_pool_s* mp_create_pool(size_t size) {
  47. struct mp_pool_s *p = NULL;
  48. int ret = posix_memalign((void **)&p, MP_ALIGNMENT, size + sizeof(mp_pool_s) + sizeof(mp_node_s));
  49. if (ret) {
  50. return NULL;
  51. }
  52. //内存池小块的大小限制
  53. p->max = (size < MP_MAX_ALLOC_FROM_POOL) ? size : MP_MAX_ALLOC_FROM_POOL;
  54. p->current = p->head;//第一块为当前块
  55. p->large = NULL;
  56. p->head->last = (unsigned char *)p + sizeof( mp_pool_s) + sizeof(mp_node_s);
  57. p->head->end = p->head->last + size;
  58. p->head->failed = 0;
  59. return p;
  60. }
  61. void mp_destory_pool( mp_pool_s *pool) {
  62. //先销毁大块
  63. mp_large_s* l = NULL;
  64. mp_node_s* p = pool->head->next, *q = NULL;
  65. for (l = pool->large; l; l = l->next) {
  66. if (l->alloc) {
  67. free(l->alloc);
  68. l->alloc = NULL;
  69. }
  70. }
  71. //然后销毁小块内存
  72. while (p) {
  73. q = p->next;
  74. free(p);
  75. p = q;
  76. }
  77. free(pool);
  78. }
  79. //申请小块内存
  80. void *mp_alloc_block(struct mp_pool_s *pool, size_t size) {
  81. unsigned char* m = NULL;
  82. size_t psize = 0;//内存池每一块的大小
  83. psize = (size_t)((unsigned char*)pool->head->end - (unsigned char*)pool->head);
  84. int ret = posix_memalign((void**)&m, MP_ALIGNMENT, psize);
  85. if (ret) return NULL;
  86. //此时已经分配出来一个新的块了
  87. mp_node_s* new_node, *p, *current;
  88. new_node = (mp_node_s*)m;
  89. new_node->end = m + psize;
  90. new_node->failed = 0;
  91. new_node->next = NULL;
  92. m += sizeof(mp_node_s);//跳过node
  93. //对于m进行地址起点内存对齐
  94. m = mp_align_ptr(m, MP_ALIGNMENT);
  95. new_node->last = m + size;
  96. current = pool->current;
  97. for (p = current; p->next; p = p->next) {
  98. if (p->failed++ > 4) {
  99. current = p->next;
  100. }
  101. }
  102. //将new_node连接到最后一块内存上, 并且尝试跟新pool->current
  103. pool->current = current ? current : new_node;
  104. p->next = new_node;
  105. return m;
  106. }
  107. //申请大块内存
  108. void *mp_alloc_large(struct mp_pool_s *pool, size_t size) {
  109. void* p = malloc(size);
  110. if (p == NULL) return NULL;
  111. mp_large_s* l = NULL;
  112. size_t cnt = 0;
  113. for (l = pool->large; l; l = l->next) {
  114. if (l->alloc) {
  115. l->alloc = p;
  116. return p;
  117. }
  118. if (cnt++ > 3) {
  119. break;//为了提高效率, 检查前5个块, 没有空闲alloc就从新申请large
  120. }
  121. }
  122. l = mp_alloc(pool, sizeof(struct mp_large_s));
  123. if (l == NULL) {
  124. free(p);
  125. return NULL;
  126. }
  127. l->alloc = p;
  128. l->next = pool->large;
  129. pool->large = l;
  130. return p;
  131. }
  132. //带有字节对齐的申请
  133. void *mp_alloc(mp_pool_s *pool, size_t size) {
  134. mp_node_s* p = NULL;
  135. unsigned char* m = NULL;
  136. if (size < MP_MAX_ALLOC_FROM_POOL) {//从小块中分配
  137. p = pool->current;
  138. do {
  139. m = mp_align_ptr(p->last, MP_ALIGNMENT);
  140. if ((size_t)(p->end - m) >= size) {
  141. p->last = m + size;
  142. return m;
  143. }
  144. p = p->next;
  145. } while (p);
  146. //说明小块中都分配失败了, 于是从新申请一个小块
  147. return mp_alloc_block(pool, size);
  148. }
  149. //从大块中分配
  150. return mp_alloc_large(pool, size);
  151. }
  152. //不带字节对齐的从内存池中申请内存
  153. void *mp_nalloc(mp_pool_s *pool, size_t size) {
  154. mp_node_s* p = NULL;
  155. unsigned char* m = NULL;
  156. if (size < MP_MAX_ALLOC_FROM_POOL) {//从小块中分配
  157. p = pool->current;
  158. do {
  159. m = p->last;
  160. if ((size_t)(p->end - m) >= size) {
  161. p->last = m + size;
  162. return m;
  163. }
  164. p = p->next;
  165. } while (p);
  166. //说明小块中都分配失败了, 于是从新申请一个小块
  167. return mp_alloc_block(pool, size);
  168. }
  169. //从大块中分配
  170. return mp_alloc_large(pool, size);
  171. }
  172. void *mp_calloc(struct mp_pool_s *pool, size_t size) {
  173. void *p = mp_alloc(pool, size);
  174. if (p) {
  175. memset(p, 0, size);
  176. }
  177. return p;
  178. }
  179. void mp_free(mp_pool_s *pool, void *p) {
  180. mp_large_s* l = NULL;
  181. for (l = pool->large; l; l = l->next) {
  182. if (p == l->alloc) {
  183. free(l->alloc);
  184. l->alloc = NULL;
  185. return ;
  186. }
  187. }
  188. }
  189. #endif

网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发