GitHub: https://github.com/storagezhang

Emai: debugzhang@163.com

华为云社区: https://bbs.huaweicloud.com/blogs/250328

内存池

内存池的存在主要就是减少调用 malloc 或者 new 的次数,减少内存分配所带来的系统开销,提升性能。

LevelDB 中的内存池是由类 Arena 实现的。Arena 先向系统申请一块大的内存,当其他组件需要申请内存时,Arena 先将已有的内存块分配给组件,如果不够用则再申请一块大的内存。当内存池对象析构时,分配的内存均被释放,这保证了内存不会泄漏。

申请内存和分配内存的区别:

  • 申请内存:向操作系统申请一块连续的内存空间。
  • 分配内存;将已经申请的内存分配给其他组件使用。

成员变量

// 指向当前内存块未分配内存的起始地址的指针
char* alloc_ptr_;
// 记录当前内存块未分配内存的大小
size_t alloc_bytes_remaining_; // 每个内存块的地址都存储在 vector 中
std::vector<char*> blocks_; // 原子变量:记录当前对象的内存总量
std::atomic<size_t> memory_usage_;

如图所示,Arena 的成员变量 blocks_ 存储若干个指针,每个指针指向一块内存。alloc_ptr_ 指向当前内存块未分配内存的起始地址,alloc_bytes_remaining_ 为当前内存块未分配内存的大小。

static const int kBlockSize = 4096;

Arena 以内存块为单位来管理内存,每个内存块的大小 kBlockSize 为 4096 KB。

构造函数与析构函数

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

构造函数初始化所有的成员变量,保证不会使用未初始化的变量。

析构函数释放 blocks_ 中每个指针指向的内存块。

内存分配接口

Arena 提供了 3 个 public 函数来简化内存分配。

Arena 的内存分配策略有三种,当申请 bytes 大小的内存时:

  • 如果 bytes 小于等于当前内存块剩余内存,直接在当前内存块上分配内存;
  • 如果 bytes 大于当前内存块剩余内存,调用 AllocateFallback 函数按照另外两种分配策略分配内存。

Allocate

inline char* Arena::Allocate(size_t bytes) {
// 不需要分配 0 字节的内存
assert(bytes > 0); // 申请的内存小于当前内存块剩余的内存,直接在当前内存块上分配内存
if (bytes <= alloc_bytes_remaining_) {
char* result = alloc_ptr_; // 从当前内存块中分配内存
alloc_ptr_ += bytes; // 计算当前内存块的剩余内存大小
alloc_bytes_remaining_ -= bytes;
return result;
} // 申请的内存大于当前内存块剩余的内存,使用 AllocateFallback 函数重新申请内存
return AllocateFallback(bytes);
}

Allocate 函数分配 bytes 大小的内存空间,返回指向所分配内存的指针。

AllocateAligned

char* Arena::AllocateAligned(size_t bytes) {
// 计算当前机器要对齐的字节数,最多 8 字节对齐,否则就按照当前机器的 void* 的大小来对齐
const int align = (sizeof(void*) > 8) ? sizeof(void*) : 8; // 字节对齐必须是 2 的次幂
// x & (x - 1) = 0 表示 x 是 2 的次幂
static_assert((align & (align - 1)) == 0,
"Pointer size should be a power of 2"); // A & (B - 1) = A % B
// reinterpret_cast<uintptr_t> 类型对应机器指针大小
size_t current_mod = reinterpret_cast<uintptr_t>(alloc_ptr_) & (align - 1); // 如果 current_mod = 0 表示 alloc_ptr_ 已经是字节对齐的
// 否则计算 align - current_mod,表示当前指针地址距离字节对齐的偏差
size_t slop = (current_mod == 0 ? 0 : align - current_mod); // 当前需要分配的字节大小加上对齐偏差就是最终需要分配的总大小
size_t needed = bytes + slop;
char* result; // 所需的内存小于当前内存块剩余的内存,直接在当前内存块上分配内存
if (needed <= alloc_bytes_remaining_) {
result = alloc_ptr_ + slop;
alloc_ptr_ += needed;
alloc_bytes_remaining_ -= needed;
} else {
// 所需的内存大于当前内存块剩余的内存,使用 AllocateFallback 函数重新申请内存
result = AllocateFallback(bytes);
} // 保证分配的内存起始地址是字节对齐的
assert((reinterpret_cast<uintptr_t>(result) & (align - 1)) == 0);
return result;
}

AllocateAligned 函数分配 bytes 大小的内存空间,且起始地址字节对齐,返回指向所分配内存的指针。

MemoryUsage

size_t MemoryUsage() const {
return memory_usage_.load(std::memory_order_relaxed);
}

MemoryUsage 函数返回当前分配给 Arena 对象的所有内存空间大小和所有指向内存块的指针大小之和。

内存分配内部实现

接上节中的 Arena 的内存分配策略,当申请 bytes 大小的内存时:

  • 如果 bytes 小于等于当前内存块剩余内存,直接在当前内存块上分配内存;
  • 如果 bytes 大于当前内存块剩余内存:
    • 如果 bytes 小于等于默认内存块大小的四分之一,新申请一个内存块,大小为默认内存块大小,在该内存块上分配内存;
    • 如果 bytes 大于默认内存块大小的四分之一,新申请一个内存块,大小为 bytes,分配内存。

AllocateFallback

char* Arena::AllocateFallback(size_t bytes) {
// 调用 AllocateNewBlock 申请一块大小为 bytes 的新内存块
if (bytes > kBlockSize / 4) {
// 在新申请的内存块中分配全部内存
char* result = AllocateNewBlock(bytes);
return result;
} // 调用 AllocateNewBlock 申请一块大小为 kBlockSize 的新内存块
alloc_ptr_ = AllocateNewBlock(kBlockSize);
alloc_bytes_remaining_ = kBlockSize; // 在新申请的内存块中分配 bytes 大小的内存
char* result = alloc_ptr_;
alloc_ptr_ += bytes;
alloc_bytes_remaining_ -= bytes;
return result;
}

当申请的内存大于当前内存块剩余内存时,AllocateFallback 函数会被调用,用来按照后两种分配策略分配内存。

这两种分配策略可以进一步减少内存分配的次数,但同时每块最后 \(\frac{1}{4}\) 的空间有可能会被浪费。

AllocateNewBlock

char* Arena::AllocateNewBlock(size_t block_bytes) {
// 申请一个大小为 block_bytes 的内存块
char* result = new char[block_bytes]; // 将该内存块的地址添加到 blocks 中
blocks_.push_back(result); // 记录当前对象内存分配总量
memory_usage_.fetch_add(block_bytes + sizeof(char*),
std::memory_order_relaxed);
return result;
}

AllocateNewBlock 函数申请一个大小为 block_bytes 的内存块。

总结

当向 Arena 申请 bytes 大小的内存时:

  • 如果 bytes 小于等于当前内存块剩余内存,直接在当前内存块上分配内存;
  • 如果 bytes 大于当前内存块剩余内存:
    • 如果 bytes 小于等于默认内存块大小的四分之一,新申请一个内存块,大小为默认内存块大小,在该内存块上分配内存;
    • 如果 bytes 大于默认内存块大小的四分之一,新申请一个内存块,大小为 bytes,分配内存。

LevelDB 源码解析之 Arena的更多相关文章

  1. LevelDB 源码解析之 Random 随机数

    GitHub: https://github.com/storagezhang Emai: debugzhang@163.com 华为云社区: https://bbs.huaweicloud.com/ ...

  2. Leveldb源码解析之Bloom Filter

    Bloom Filter,即布隆过滤器,是一种空间效率很高的随机数据结构. 原理:开辟m个bit位数组的空间,并全部置零,使用k个哈希函数将元素映射到数组中,相应位置1.如下图,元素K通过哈希函数h1 ...

  3. LevelDB 源码解析之 Varint 编码

    GitHub: https://github.com/storagezhang Emai: debugzhang@163.com 华为云社区: https://bbs.huaweicloud.com/ ...

  4. LevelDB源码剖析

    LevelDB的公共部件并不复杂,但为了更好的理解其各个核心模块的实现,此处挑几个关键的部件先行备忘. Arena(内存领地) Arena类用于内存管理,其存在的价值在于: 提高程序性能,减少Heap ...

  5. leveldb源码分析--SSTable之TableBuilder

    上一篇文章讲述了SSTable的格式以后,本文结合源码解析SSTable是如何生成的. void TableBuilder::Add(const Slice& key, const Slice ...

  6. 【JUC源码解析】Exchanger

    简介 Exchanger,并发工具类,用于线程间的数据交换. 使用 两个线程,两个缓冲区,一个线程往一个缓冲区里面填数据,另一个线程从另一个缓冲区里面取数据.当填数据的线程将缓冲区填满时,或者取数据的 ...

  7. Leveldb源码分析--1

    coming from http://blog.csdn.net/sparkliang/article/details/8567602 [前言:看了一点oceanbase,没有意志力继续坚持下去了,暂 ...

  8. Caffe2源码解析

    写在前面 上一篇文章对Caffe2中的core模块进行了简单拆解Caffe2源码解析之core,本篇给出其它模块的拆解,目的是大致了解每个模块的内容和目标,进一步理解Caffe2的整体框架.内容不多, ...

  9. 【原】Android热更新开源项目Tinker源码解析系列之三:so热更新

    本系列将从以下三个方面对Tinker进行源码解析: Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Android热更新开源项目Tinker源码解析系列之二:资源文件热更新 A ...

随机推荐

  1. Next.js SSR Tutorials

    Next.js SSR Tutorials https://codesandbox.io/s/nextjs-demo-h49zt cli $ npx create-next-app ssr-demo- ...

  2. element-ui UI 组件库剖析

    element-ui UI 组件库剖析 /* Automatically generated by './build/bin/build-entry.js' */ https://github.com ...

  3. Apple CSS Animation All In One

    Apple CSS Animation All In One Apple Watch CSS Animation https://codepen.io/xgqfrms/pen/LYZaNMb See ...

  4. how to measure function performance in javascript

    how to measure function performance in javascript Performance API Performance Timeline API Navigatio ...

  5. Flutter Search Component

    Flutter Search Component flutter 搜索组件 xgqfrms 2012-2020 www.cnblogs.com 发布文章使用:只允许注册用户才可以访问!

  6. HTML5 & canvas fingerprinting

    HTML5 & canvas fingerprinting demo https://codepen.io/xgqfrms/full/BaoMWMp window.addEventListen ...

  7. Mac mini M1使用简单体验(编程、游戏、深度学习)

    好久不见了各位! 前一阵子忍不住剁手买了M1芯片的mac mini,为了弥补自己的内疚感就卖了自己的旧的mbp2017款.数据也完全迁移到了新机器上,之前的工作也就由mbp2017彻底换成mac mi ...

  8. CMD 中运行 xx 命令提示 不是内部或外部命令,也不是可运行的程序或批处理文件的问题

    出现这个问题的原因一般有2个 这个命令依赖某个软件,而你又没有安装 这里你只需要去下载安装好对应的软件,基本上就可以解决上面的问题了. 软件安装好了,但是需要配置环境变量 第二个原因就按照下图,去设置 ...

  9. Prometheus时序数据库-内存中的存储结构

    Prometheus时序数据库-内存中的存储结构 前言 笔者最近担起了公司监控的重任,而当前监控最流行的数据库即是Prometheus.按照笔者打破砂锅问到底的精神,自然要把这个开源组件源码搞明白才行 ...

  10. 生成类库项目时同时生成的pdb文件是什么东东?

    英文全称:Program Database File Debug里的PDB是full,保存着调试和项目状态信息.有断言.堆栈检查等代码.可以对程序的调试配置进行增量链接.Release 里的PDB是p ...