当前位置:   article > 正文

内存池原理与实现

内存池

什么是内存池

应用程序直接对使用的内存进行管理,避免频繁的从堆上申请释放内存,可以做到一次申请,多次分配。内存池主要是针对小块。

内存池管理的是堆内存。进程开始运行的时候,划分出一块内存

为什么需要内存池?

对于服务器,如果不段有客户端连接,并且客户端不断发送消息,对于每个连接的每个消息,服务器都需要去malloc/free内存。如果服务器需要7*24运行,时间久了,就会产生很多内存碎片,没有整块,最后就有可能malloc比较大的内存的时候失败。最后进程就会coredump。

这种问题,出现了很难解,因为程序要运行很长时间才会出现。解决方案就是使用内存池。

频繁的malloc/free, 会造成哪些问题?

  1. 不利于内存管理
  2. 内存碎片
  3. 出现内存泄漏,不容易查出具体位置。

内存池使用场景

典型的场景,服务器处理网络io。message较大的时候,recv、parse后,需要push到另一个thread进行处理。使用栈内存不合适,需要申请堆内存。recv的时候申请buffer,send后,释放buffer。每来一个消息,都需要申请、释放内存。

内存池的实现

内存池的演化

  1. 最早的内存池雏形。

每次malloc内存后,加入到一个内存池链表中,free的时候不释放,只是修改flag标志,表示当前内存块是空闲的。一定程度解决内存碎片的问题。

存在的问题

1)内存块越分越小。

如果一个内存块64字节,现在应用程序需要54字节,那么剩余的10字节又会加入到内存池链表的尾部。就会造成出现很多小块内存,越分越小,出现很多没办法使用的小块内存。链表也会越来越长。

2. 分配固定大小的不同的块

内存池内分别有16 byte,32byte...,这种固定大小的内存块。应用程序要申请内存,到相应大小的块中去获取。如果要申请大于1024 byte的内存,直接就申请一整块。这样就能够解决出现内存块越分越小的问题。

3. 用hash table

这种方法有什么缺点?

1) 查找速度慢,

malloc的时候,需要查找空闲的内存块,即flag为0

free的时候需要查找,设置flag = 0.

free的时候,参数加上size,就可以在hash table中找到相应的slot。

解决方法:

a. 对于malloc时查找,可以将每一个slot中的内存块分为两个链表,一个是已使用的,一个是未使用的。

b. free时候的查找,可以用hash,rbtree。

2)内存存在浪费,会存在间隙。影响小块的回收。

小块有没有必要回收?小块内存回收是一个极其麻烦的事情。

如果一个连接对应一个内存池,连接的生命周期不会很长,可以不回收小块内存。

如果要使用全局内存池,可以使用jemalloc、tcmalloc。

解决小块回收的方法:

对于16bytes的slot,下面挂的是4k的内存块, 即一开始就分配一个整块,每一个从这个内存块里面分配16bytes,如果4k用完了,接着再申请4k内存块,挂到链表上。

实现一个内存池

我们的实现比较粗犷,对于小块内存,没有单独回收,要回收统一回收,不单独回收一个块内的某个小块内存。对于大块,进行回收。

内存池里面保持两个list,一个是小块,一个是大块。小块大小固定是4k,大块用于大于4k的内存。如果申请小于4k的内存,直接从小块进行分配,一次申请好4k,之后在这个基础上进行分配;申请大于4k的内存,则使用大块,用多少申请多少,大块的大小不是固定的。

对于一个网络连接,我们create一个内存池,等到连接断开,destroy内存池。

代码实现

 

  1. #include <stdlib.h>
  2. #define ALIGNMENT 8
  3. typedef struct _mp_node_s {
  4. unsigned char *last; // 内存块中未分配内存的首地址
  5. unsigned char *end; // 内存块的尾地址
  6. struct _mp_node_s *next;
  7. } mp_node_s;
  8. typedef struct _mp_large_s {
  9. struct _mp_large_s *next;
  10. void *alloc;
  11. } mp_large_s;
  12. typedef struct _mp_pool_s {
  13. mp_node_s *small;
  14. mp_large_s *large;
  15. int size;
  16. } mp_pool_s;
  17. void *mp_malloc(mp_pool_s *pool, int size);
  18. mp_pool_s *mp_create_pool(int size) {
  19. mp_pool_s *pool;
  20. int ret = posix_memalign((void **)&pool, ALIGNMENT, size + sizeof(mp_pool_s));
  21. if (ret) return NULL;
  22. pool->small = (mp_node_s *)(pool + 1);
  23. pool->small->last = (unsigned char *)(pool->small + 1);
  24. pool->small->end = (unsigned char *)pool + size + sizeof(mp_pool_s);
  25. pool->small->next = NULL;
  26. pool->large = NULL;
  27. return pool;
  28. }
  29. void mp_destroy_pool(mp_pool_s *pool) {
  30. mp_large_s *large;
  31. for (large = pool->large; large; large = large->next) {
  32. if (large->alloc) {
  33. free(large->alloc);
  34. }
  35. }
  36. mp_node_s *small, *next;
  37. for (small = pool->small; small;) {
  38. next = small->next;
  39. free(small);
  40. small = next;
  41. }
  42. free(pool);
  43. }
  44. static void *mp_malloc_large(mp_pool_s *pool, int size) {
  45. mp_large_s *large = (mp_large_s *)mp_malloc(pool, sizeof(mp_large_s));
  46. int ret = posix_memalign((void **)&large->alloc, ALIGNMENT, size);
  47. if (ret) return NULL;
  48. return large->alloc;
  49. }
  50. static void *mp_malloc_small(mp_pool_s *pool, int size) {
  51. mp_node_s *node = NULL;
  52. int ret = posix_memalign((void **)&node, ALIGNMENT, pool->size);
  53. if (ret) return NULL;
  54. node->next = pool->small;
  55. pool->small = node;
  56. node->last = (unsigned char *)node + sizeof(mp_node_s);
  57. node->end = (unsigned char *)node + pool->size;
  58. return node->last;
  59. }
  60. void *mp_malloc(mp_pool_s *pool, int size) {
  61. if (size < pool->size) {
  62. mp_node_s *node = pool->small;
  63. if (size < node->end - node->last) { // 需要考虑字节对齐
  64. unsigned char *m = node->last;
  65. node->last = m + size;
  66. return m;
  67. } else {
  68. return mp_malloc_small(pool, size);
  69. }
  70. } else {
  71. return mp_malloc_large(pool, size);
  72. }
  73. }
  74. void *mp_free(mp_pool_s *pool, void *p) {
  75. mp_large_s *large = NULL;
  76. for (large = pool->large; large; large = large->next) {
  77. if (large->alloc == ()p) {
  78. free(large->alloc);
  79. large->alloc = NULL;
  80. break;
  81. }
  82. }
  83. }
  84. int main() {
  85. }

如何保证内存池线程安全?

加锁来保证内存池线程安全,锁的粒度需要把握好。

如果是单线程reactor,或者没个线程一个reactor,也就是一个连接只在一个线程处理,不需要考虑加锁,一个连接一个线程池,是线程安全的。

开源内存池

如果要使用全局内存池,可以使用开源内存池

  1. jemalloc
  2. tcmalloc

使用的时候,加上一个宏定义,不需要改源代码,malloc/free就被hook住,使用jemalloc/tcmalloc的函数。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/292401
推荐阅读
相关标签
  

闽ICP备14008679号