内存池的一些思考与总结

allocate需要如下需求:

  1. 如何设计内存池;
  2. 如何设计字节对齐;
  3. 如何设计统计内存使用情况;(待完成)
  4. 如何设计单元测试验证内存池的正确性。(待完成)

一些技术问题:

  1. malloc是已经实现内存对齐了,问如何实现的,怎么证明?(未解决)
  2. free和delete是如何实现的,如何知道释放多少内存。(未解决)

一 如何设计内存池

内存池一般而言,即是大块内存直接分配,小块内存则先申请一个大块内存,然后不断地使用这个大块内存直至用完。基本算法如下:

inline char* Arena::Allocate(size_t bytes) {
  // The semantics of what to return are a bit messy if we allow
  // 0-byte allocations, so we disallow them here (we don't need
  // them for our internal use).
  assert(bytes > 0);
  if (bytes <= alloc_bytes_remaining_) {          //是否能从可用内存中获取
    char* result = alloc_ptr_;
    alloc_ptr_ += bytes;
    alloc_bytes_remaining_ -= bytes;
    return result;
  }
  return AllocateFallback(bytes);                    //不能,去申请内存
}

大块内存一定无法从可用内存中获取,小块内存则需要看剩余内存大小,如果剩余量小于需求,则也需要申请,否则,不需要申请。

所以小块内存共用的大块内存的使用情况,需要使用如下两个变量来标识:

  // Allocation state
  char* alloc_ptr_;                                            //大块内存的可用位置;
  size_t alloc_bytes_remaining_;                    //当前大块内存还剩余多少

简化了小块内存的分配,大块内存的分配逻辑如下:

char* Arena::AllocateFallback(size_t bytes) {
  if (bytes > kBlockSize / 4) {
    // Object is more than a quarter of our block size.  Allocate it separately
    // to avoid wasting too much space in leftover bytes.
    char* result = AllocateNewBlock(bytes);
    return result;
  }

  // We waste the remaining space in the current block.
  alloc_ptr_ = AllocateNewBlock(kBlockSize);
  alloc_bytes_remaining_ = kBlockSize;

  char* result = alloc_ptr_;
  alloc_ptr_ += bytes;
  alloc_bytes_remaining_ -= bytes;
  return result;
}

char* Arena::AllocateNewBlock(size_t block_bytes) {        //所有申请内存的操作都在这;
  char* result = new char[block_bytes];
  blocks_.push_back(result);
  memory_usage_.NoBarrier_Store(
      reinterpret_cast<void*>(MemoryUsage() + block_bytes + sizeof(char*)));
  return result;
}

申请大块内存有两种情况:
(1)用户本身申请的就是大内存;
(2)用户申请的是小内存,但是小内存所使用的大内存块用完了。

对于第一种情况,直接调用AllocateNewBlock方法,申请出那么大的内存来并交给用户就可以了。
对于第二种情况,调用完AllocateNewBlock方法申请固定大小内存(kBlockSize),还需要设置 alloc_ptr_和alloc_bytes_remaining_变量;最后在这块新申请的大内存中分配内存给用户。

内存的释放是统一在一起进行的,位于分配器的析构函数中:

Arena::~Arena() {
  for (size_t i = 0; i < blocks_.size(); i++) {
    delete[] blocks_[i];
  }
}

blocks_定义如下:

  // Array of new[] allocated memory blocks
  std::vector<char*> blocks_;

blocks_包含了所有AllocateNewBlock方法申请的内存,最后统一释放。

一些问题:

  1. 小内存共用的大块内存是多大,即一次申请多大合适?(相关的论文查询
    leveldb中是这个大小:
static const int kBlockSize = 4096;
  1. 申请多大的内存适合直接返回给用户,而不是共用大内存块?
    这种分为两种情况:
    (1)存在可用大块内存时,只要大块内存够用,就都是直接返回给用户;
    (2)不存在可用的大块内存时,超过kBlockSize的1/4,就直接申请相应大小的内存返回给用户,而不是当作小内存来申请kBlockSize大小的内存。

  2. 以上策略存在内存的浪费,具体表现为:
    当用户新申请的内存不在可用内存大小范围内,且小于kBlockSize时,那一段可用内存就被浪费了,去新申请新的内存了, alloc_ptr_从新内存开始。

  3. 内存是最后统一释放的,所以是不是需要一些策略来提前释放以节约内存?具体的释放时机是什么?

二 如何设计字节对齐

malloc分配的内存一定是对齐的,含义是其返回的地址一定是2的幂次(内存对齐的大小一般是指针的大小,指针的大小一定是2的幂次)。

所以对齐的大小align求法:

const int align = (sizeof(void*) > 8) ? sizeof(void*) : 8;

对齐之后的地址一定满足(align - 1的值中1的个数代表了返回值指针最后的0的个数):

  assert((reinterpret_cast<uintptr_t>(result) & (align-1)) == 0);

具体算法如下:

char* Arena::AllocateAligned(size_t bytes) {
  const int align = (sizeof(void*) > 8) ? sizeof(void*) : 8;
  assert((align & (align-1)) == 0);   // Pointer size should be a power of 2
  size_t current_mod = reinterpret_cast<uintptr_t>(alloc_ptr_) & (align-1);   //alloc_ptr_的后n位,n为对齐的位数    
  size_t slop = (current_mod == 0 ? 0 : align - current_mod);                    //对齐需要空余的字节数
  size_t needed = bytes + slop;
  char* result;
  if (needed <= alloc_bytes_remaining_) {          //类似于上一小节的Allocate
    result = alloc_ptr_ + slop;
    alloc_ptr_ += needed;
    alloc_bytes_remaining_ -= needed;
  } else {
    // AllocateFallback always returned aligned memory
    result = AllocateFallback(bytes);              //调用malloc的一定是内存对齐的;
  }
  assert((reinterpret_cast<uintptr_t>(result) & (align-1)) == 0);
  return result;
}

函数同样分为两部分来处理:
(1)申请内存大小 + 对齐字节数 > 可用内存大小,此时直接调用malloc去申请(此时可能会造成内存的浪费),此时一定是内存对齐的;
(2)申请内存大小 + 对齐字节数 < 可用内存大小,手动对齐:将alloc_ptr_调整到对齐位置,作为返回结果,再调整alloc_ptr_到下一可用位置。其中对齐的结果是返回值对齐的,最后的alloc_ptr_不是对齐的。

三 如何设计统计内存使用情况;

内存的使用需要进行统计,以方便进行监控以及内存泄露的查询。

内存的统计要求所有的内存申请工作都需要在同一个函数中进行:AllocateNewBlock。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,817评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,329评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,354评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,498评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,600评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,829评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,979评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,722评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,189评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,519评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,654评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,940评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,762评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,993评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,382评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,543评论 2 349

推荐阅读更多精彩内容