跳过正文
  1. Posts/

levelDB 代码阅读笔记 05 arena

·3 分钟
目录
108 - 这篇文章属于一个选集。

arena时levelDB中的内存池实现

接口
#

没有太多好说的,都非常直观。 补了些注释

 1
 2class Arena {
 3 public:
 4  Arena();
 5
 6  Arena(const Arena&) = delete;
 7  Arena& operator=(const Arena&) = delete;
 8
 9  ~Arena();
10
11  // Return a pointer to a newly allocated memory block of "bytes" bytes.
12  char* Allocate(size_t bytes);
13
14  // Allocate memory with the normal alignment guarantees provided by malloc.
15  char* AllocateAligned(size_t bytes);
16
17  // Returns an estimate of the total memory usage of data allocated
18  // by the arena.
19  size_t MemoryUsage() const {
20    return memory_usage_.load(std::memory_order_relaxed);
21  }
22
23 private:
24  char* AllocateFallback(size_t bytes);
25  char* AllocateNewBlock(size_t block_bytes);
26
27  // Allocation state
28  // alloc_ptr_ 表示下一个可用的位置
29  char* alloc_ptr_;
30  size_t alloc_bytes_remaining_;
31
32  // Array of new[] allocated memory blocks
33  // 需要注意,vector中每一个元素都是一段内存块(memory, block)
34  std::vector<char*> blocks_;
35
36  // Total memory usage of the arena.
37  //
38  // TODO(costan): This member is accessed via atomics, but the others are
39  //               accessed without any locking. Is this OK?
40  std::atomic<size_t> memory_usage_;
41};

分配策略
#

代码可说的不多,主要想讲讲分配策略

之前玩nvJPEG的时候也实现过一个简单的显存池从而避免过多的cudamalloc

当时的策略是

  1. 提前分配好很多种不同size的block. 每次分配就二分找到最合适大小的未被使用的block,然后将这个block标记为已使用.
  2. 某个block在使用完会被再次标记为unused,从而可以再次使用
  3. 如果某次分配需要的size找不到合适的block,会额外分配一个block. 分配的size会比实际需要的略大,避免下次出现比当前需要size大一点的时候还需要额外分配block
  4. 每个block在同一时间内最多只会被一个handle占用。 不会存在将一个Block分配给多个handle使用的情况

而levelDB中的arena中的分配策略很不相同

  1. 对于大size,单独分配恰好符合需要的block
  2. 不提供清理内存的接口,只有在析构的时候释放内存
  3. 同一个block可能会被多个handle使用
  4. block只有一种固定大小

这种策略其实简单了很多,而且省去了分配内存的时候查找合适size block的开销

一些细节补充在代码注释中了

 1
 2
 3static const int kBlockSize = 4096;
 4// 整体分配逻辑是默认每个block KblockSize个、 当目前的block不足当前的需要时,就舍弃掉,分配新的。
 5// 但是如果需要的比较大时,就单独分配一个大block,避免过多的浪费。
 6// note: 实际上只有小内存才能受益于内存池,大内存每次都要allocate,和自己手动new没区别
 7
 8Arena::Arena()
 9    : alloc_ptr_(nullptr), alloc_bytes_remaining_(0), memory_usage_(0) {}
10
11Arena::~Arena() {
12  for (size_t i = 0; i < blocks_.size(); i++) {
13    delete[] blocks_[i];
14  }
15}
16
17char* Arena::AllocateFallback(size_t bytes) {
18  if (bytes > kBlockSize / 4) {
19    // 这里保证了浪费最多不超过 kBlockSize/4
20    // Object is more than a quarter of our block size.  Allocate it separately
21    // to avoid wasting too much space in leftover bytes.
22    // 因为下面的逻辑是新分配block size个,当前的block的全都扔掉,
23    char* result = AllocateNewBlock(bytes);
24    return result;
25  }
26
27  // We waste the remaining space in the current block.
28  // 这里当前Block不够的那些就全都扔掉了
29  alloc_ptr_ = AllocateNewBlock(kBlockSize);
30  alloc_bytes_remaining_ = kBlockSize;
31
32  char* result = alloc_ptr_;
33  alloc_ptr_ += bytes;
34  alloc_bytes_remaining_ -= bytes;
35  return result;
36}
37
38char* Arena::AllocateAligned(size_t bytes) {
39  const int align = (sizeof(void*) > 8) ? sizeof(void*) : 8;
40  static_assert((align & (align - 1)) == 0,
41                "Pointer size should be a power of 2");
42  // &(align-1)相当于 %align  因为这里align这里一定时2的倍数
43  size_t current_mod = reinterpret_cast<uintptr_t>(alloc_ptr_) & (align - 1);
44  // slophi计算还差几个字节能把当前开始的分配起点alloc_ptr_ 调整到最近的aligned 地址
45  size_t slop = (current_mod == 0 ? 0 : align - current_mod);
46  size_t needed = bytes + slop;
47  char* result;
48  if (needed <= alloc_bytes_remaining_) {
49    result = alloc_ptr_ + slop;
50    alloc_ptr_ += needed;
51    alloc_bytes_remaining_ -= needed;
52  } else {
53    // AllocateFallback always returned aligned memory
54    result = AllocateFallback(bytes);
55  }
56  assert((reinterpret_cast<uintptr_t>(result) & (align - 1)) == 0);
57  return result;
58}
59
60char* Arena::AllocateNewBlock(size_t block_bytes) {
61  char* result = new char[block_bytes];
62  blocks_.push_back(result);
63  memory_usage_.fetch_add(block_bytes + sizeof(char*),
64                          std::memory_order_relaxed);
65  return result;
66}
108 - 这篇文章属于一个选集。

相关文章

levelDB 代码阅读笔记 04 filter

·6 分钟
FilterPolicy接口 # 1 2class LEVELDB_EXPORT FilterPolicy { 3 public: 4 virtual ~FilterPolicy(); 5 6 // Return the name of this policy. Note that if the filter encoding 7 // changes in an incompatible way, the name returned by this method 8 // must be changed. Otherwise, old incompatible filters may be 9 // passed to methods of this type. 10 virtual const char* Name() const = 0; 11 12 // keys[0,n-1] contains a list of keys (potentially with duplicates) 13 // that are ordered according to the user supplied comparator. 14 // Append a filter that summarizes keys[0,n-1] to *dst. 15 // 16 // Warning: do not change the initial contents of *dst. Instead, 17 // append the newly constructed filter to *dst. 18 virtual void CreateFilter(const Slice* keys, int n, 19 std::string* dst) const = 0; 20 21 // "filter" contains the data appended by a preceding call to 22 // CreateFilter() on this class. This method must return true if 23 // the key was in the list of keys passed to CreateFilter(). 24 // This method may return true or false if the key was not on the 25 // list, but it should aim to return false with a high probability. 26 virtual bool KeyMayMatch(const Slice& key, const Slice& filter) const = 0; 27}; 其中CreateFilter的含义是从n个key生成一个 std::string. 生成的std::string可以包含n个key的信息(类似于生成了一个全集) 从而后续判断某个key是否在其中。

levelDB 代码阅读笔记 01 db.h

·4 分钟
背景 # 最近在做一个智能算力相关的项目,类似美团外卖广告智能算力的探索与实践 其中实现控制系统需要与数据库交互。 虽然最后技术选型并没有使用到levelDB,但是想趁机把代码读了吧。

levelDB 使用笔记

·6 分钟
2022-02-26 update: 说学习笔记听起来像在分析代码。。。但是实际上什么都没干,还是写"使用笔记"好了