博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
leveldb代码精读 内存池Arena
阅读量:2433 次
发布时间:2019-05-10

本文共 5730 字,大约阅读时间需要 19 分钟。

代码文件
util/arena.h
util/arena.cc
level db有个内存池不叫xxxpool,而叫Arena。
从操作系统申请内存的方式是直接new一个空的char数组,得到一大块连续内存作为数据块,并维护数据块内的空间使用情况。
当有app申请内存时,将数组里某个char的地址返回给app,让它使用从这个char开始的一段连续内存。
当一个数据块不能满足app的连续内存需求时,就会创建新的char数组。
每个char数组,也就是数据块的地址都记录在Arena的一个vector里。当Arena销毁时,就根据vector里记录的数据块地址逐个释放空间。
成员变量
  1. std::vector<char*> blocks_; // 已分配的内存块的地址队列
  2. size_t blocks_memory_; // 已分配所有内存块的总内存
  3. char* alloc_ptr_; // 当前内存块下次分配的地址。比如已经分配了4个单位,alloc_ptr_就指向5
  4. size_t alloc_bytes_remaining_; // 当前块内还剩多少内存
  5. static const int kBlockSize = 4096; // 默认的标准块是4k,也支持按程序要求申请指定大小的块
私有函数
  1. char* AllocateNewBlock(size_t block_bytes); //按照给定大小分配一个新数据块,将新块的地址推入blocks_
  2. char* AllocateFallback(size_t bytes); //当前块的alloc_bytes_remaining_小于请求的内存大小时,用这个函数分配新内存
公共函数
  1. char* Allocate(size_t bytes); //从当前块中分配内存,如果内存不够就调用AllocateFallback
  2. char* AllocateAligned(size_t bytes); //和Allocate类似,但是这个会做内存对齐
  3. size_t MemoryUsage() const {
    ... //返回当前已分配的内存块。是估算值,以创建内存块但是还没分配给app使用的部分也算在内。
MemoryUsage函数的内容很简单,就是返回blocks_memory_加上blocks_里存放的指针大小之和
  1. size_t MemoryUsage() const {
  2.     return blocks_memory_ + blocks_.capacity() * sizeof(char*);
  3.   }
  
A
rena最基本的操作是分配一个新内存块。
只是申请一个块新内存,并不更新alloc_ptr_和alloc_bytes_remaining_ 。
内存块的形式就是简单地以char数组的形式占用一片连续内存。
  1. char* Arena::AllocateNewBlock(size_t block_bytes) {
  2.   // 用空的char数组占用一片连续内存,将地址保存到result里,当做返回值。
  3.   char* result = new char[block_bytes];
  4.   // 整个Arena已分配的总内存需要加上新申请的块的大小
  5.   blocks_memory_ += block_bytes;
  6.   // 将新块的地址追加到blocks_队列
  7.   blocks_.push_back(result);
  8.   返回新块地址
  9.   return result;
  10. }
AllocateNewBlock只是最基本的分配新块,没办法实用,比如给app分配内存时关键的alloc_ptr_ 和alloc_bytes_remaining_ 它不会维护。
Arena内部分配新块的功能函数实际是AllocateFallback。通过AllocateFallback调用AllocateNewBlock
  1. char* Arena::AllocateFallback(size_t bytes) {
  2.   // 它会先看一下要分配多大内存。如果是大于标准块的四分之一,就直接新建一个这么大内存的非标准块,给app专用了。
  3.   // 剩下的一起都有app自己解决,Arena保持当前块的alloc_ptr_和alloc_bytes_remaining_,给以后分配小空间使用。
  4.   if (bytes > kBlockSize / 4) {
  5.     char* result = AllocateNewBlock(bytes);
  6.     return result;
  7.   }
  8.   // 如果申请的bytes小于标准块的四分之一,并且剩余空间alloc_bytes_remaining_还是不够,就创建新的标准块,将地址赋给alloc_ptr_。
  9.   // 新的标准块有Arena维护,作为当前块给app分配内存。alloc_bytes_remaining_自然就是标准块大小。
  10.   alloc_ptr_ = AllocateNewBlock(kBlockSize);
  11.   alloc_bytes_remaining_ = kBlockSize;
  12.   char* result = alloc_ptr_; // 由于是新的标准块,因此块的起始地址就是给app分配内存的起始地址,作为返回值。
  13.   alloc_ptr_ += bytes; // alloc_ptr_后移bytes,作为以后分配内存的起始位置。
  14.   alloc_bytes_remaining_ -= bytes; // 块内剩余内存减少已分配给app的bytes
  15.   return result;
  16. }
外部app调用的是Allocate函数。
由于Arena记录了当前可分配的起始地址alloc_ptr_,以及当前块剩余的内存alloc_bytes_remaining_,因此只需要操作这两个值即可。
blocks_memory_在分配新块的时候已经加过块大小了,就不用每次分配小内存都去维护了。
  1. inline char* Arena::Allocate(size_t bytes) {
  2.   assert(bytes > 0);
  3.   if (bytes <= alloc_bytes_remaining_) {
  4.     char* result = alloc_ptr_; // alloc_ptr_是当前可分配内存的起始地址,肯定就是新分配内存的地址了。
  5.     alloc_ptr_ += bytes; // 分配后alloc_ptr_需要向后移bytes,执行下一次分配的起始地址。
  6.     alloc_bytes_remaining_ -= bytes; // 块内剩余内存减少了bytes
  7.     return result;
  8.   }
  9.   return AllocateFallback(bytes); // 如果app申请的内存大于块内剩余内存,就调用AllocateFallback来申请新块。
  10. }
以上就是Arena自身创建数据块,以及将数据块内的空间分配给app使用的过程。
Arena里还有一个函数,和Allocate一样供外部app调用,区别是它会做内存对齐。
关于内存对齐,看下面一段程序。
  1. #include <stdio.h>
  2. #include <unistd.h>
  3. struct st {
  4.      char c;
  5.      int i;
  6. };
  7. int main()
  8. {
  9.      char c = 'A';
  10.      int i = 100;
  11.      struct st st1;
  12.      printf("size char : %d\n", sizeof(c));
  13.      printf("size int : %d\n", sizeof(i));
  14.      printf("size st : %d\n", sizeof(st1));
  15.      printf("addr st1.c : %x\n", &(st1.c));
  16.      printf("addr st1.i : %x\n", &(st1.i));
  17.      return 0;
  18. }
输出结果
[root@mysql1 c]# ./a
size char   : 1
size int    : 4
size st     : 8
addr st1.c  : a5334f40
addr st1.i  : a5334f44
从结果看,char占1个字节,int占四个。但是结构体的体积确实8。
打印结构体内里char c和int i的地址,会发现char c实际占用了4字节。
上面就是系统自动做了内存对齐,在char c后面填了3字节。
Arena在创建new char[block_bytes]这么大的块后,需要在里面为app分配内存。
由于块内的空间是Arena自己维护的,系统不会做内存对齐,就需要Arena自己对齐。
下面是Arena内存对齐版的Allocate函数
  1. char* Arena::AllocateAligned(size_t bytes) {
  2.   const int align = (sizeof(void*) > 8) ? sizeof(void*) : 8;
  3.   assert((align & (align-1)) == 0); // Pointer size should be a power of 2
  4.   size_t current_mod = reinterpret_cast<uintptr_t>(alloc_ptr_) & (align-1);
  5.   size_t slop = (current_mod == 0 ? 0 : align - current_mod);
  6.   size_t needed = bytes + slop;
  7.   char* result;
  8.   if (needed <= alloc_bytes_remaining_) {
  9.     result = alloc_ptr_ + slop;
  10.     alloc_ptr_ += needed;
  11.     alloc_bytes_remaining_ -= needed;
  12.   } else {
  13.     // AllocateFallback always returned aligned memory
  14.     result = AllocateFallback(bytes);
  15.   }
  16.   assert((reinterpret_cast<uintptr_t>(result) & (align-1)) == 0);
  17.   return result;
  18. }
Allocate做判断时直接拿app申请的内存大小bytes与alloc_bytes_remaining_比较。
而AllocateAligned是先把bytes对齐,然后再和alloc_bytes_remaining_比较。
下面逐行分析内存对齐的过程
  1. const int align = (sizeof(void*) > 8) ? sizeof(void*) : 8;
首先需要确认按多少对齐。必须保证对齐单位必须能放下一个完整的指针(void* ),如果指针的体积小于8,就按8字节对齐。
在我的环境里sizeof(void*)就是8字节,下面都已8字节为例。
  1. assert((align & (align-1)) == 0);
这句是要求这个对齐单位必须是2的n次方。也即是说它的2进制必须是一个1后面跟的全是0。
比如从8开始
8: 1000
16:10000
...
...
  1. size_t current_mod = reinterpret_cast<uintptr_t>(alloc_ptr_) & (align-1);
这句的意思是看看alloc_ptr_的“零头”是多少
以10进制为例
如果以10为标准(align = 10),大于等于10的部分都不用看,只看小于10(也就是align-1)的部分有没有值。因此108的零头是8。
换到2进制
1 当前分配的起始地址是alloc_ptr_,把alloc_ptr_强行转换成长整形。
2 对齐单位以8为例,align-1就是7,也就是111
3 reinterpret_cast<uintptr_t>(alloc_ptr_) & (align-1)
由于align-1 只有低三位是1,其它全是0。和它按位与过之后,大于align-1的部分就全没了,只保留“零头”
alloc_ptr_作为地址,它的二进制可能是 ...101111010这种形式
计算后就得到了“零头”
  ...10111
010
& ...00000
111
--------------
  ...00000
010
4 计算之后,发现上次分配后,多出的“零头” current_mod = 2
size_t slop = (current_mod == 0 ? 0 : align - current_mod);
用对其单位align减去零头,就得到了需要手工填补的字节数。
上面例子里的结构体,char c是1字节,对齐单位是4,就填补了4-1=3字节。
我们的对齐单位是align,因此需要填align - "零头"current_mod。
  1. size_t needed = bytes + slop;
app请求的内存bytes加上需要填补的slop,就得到了实际需要分配的内存量。
  1. char* result;
  2.   if (needed <= alloc_bytes_remaining_) {
  3.      result = alloc_ptr_ + slop;
  4. ...
下面就和Allocate函数差不多了,只是返回给app的地址result不是alloc_ptr_ ,而是alloc_ptr_ 加上补充的空字节后的。

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/26239116/viewspace-1832774/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/26239116/viewspace-1832774/

你可能感兴趣的文章
Thymeleaf中一个页面怎么嵌套另一个页面,关于页面嵌套,标签告诉你应该知道的
查看>>
JAVA如何实现短信验证码--阿里云接口,新手式图文教学,个人项目有这一篇就够了
查看>>
Java中大小数BigDecimal的加减乘除用法及场景的详细介绍,看完不信你还会报Syntax error on token “+/-/*“, invalid AssignmentOperat异常
查看>>
UVa 10917 Dijkstra
查看>>
CF403B/CF402D
查看>>
CF402E / 403C
查看>>
cf404b
查看>>
cf404c
查看>>
cf404d
查看>>
武大网络预赛 Problem 1545 - I - Twenty-four
查看>>
ZOJ Problem Set - 3768 Continuous Login
查看>>
某山面试 3、实现如下函数:
查看>>
malloc的小知识
查看>>
UVALive 6755 - Swyper Keyboard
查看>>
uva_11029 Leading and Trailing 快速幂 数的n次方前几位
查看>>
uva10023 手算开方的方法
查看>>
欧拉函数——从容斥定理和积性函数的性质谈开
查看>>
容斥原理 带禁止位的排列
查看>>
第一个JSP程序(JSP入门)
查看>>
JSP语法简介
查看>>