时间:2021-07-01 10:21:17 帮助过:27人阅读
内存分配策略:
1. 当需剩余的内存大小满足分配需求时,直接使用剩余的内存(之前一次性申请了一大块,还有些没用完)
否则需要向系统重新申请一块。
2. 当前块剩余的内存大小不满足分配需求,并且需要分配的内存比较大时(>4096/4 = 1k),单独申请一块独立的内存。
3. 当前块剩余的内存不够并且新的分配需求不大于1k时, 另外申请一大块4k,从中取出部分返回给调用者,余下的供下次使用。
源码的注释中也说到了,上面第2点是为了避免过多的内存浪费,为什么这么做就能避免呢? 考虑一种情况:
假如当前块还剩余1k大小,分配需求是 1025 bytes > 1k, 不按上面的做法的话,就需要新申请一个4k块从中取出1025 bytes返回,然而这么做的话,上一块剩余的1k就再也不会被使用了,这就是浪费。 反而之前剩余的1k内存还可以继续使用。
因此这种做法避免了大块的浪费,然而仍有可能浪费1k之内的内存,为什么不把这个值设的很小呢? 那就和直接使用new差不多了,失去了内存分配器的原有意义,设置成这个值是一个权衡利弊的结果。
具体实现:
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); } 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; }
此外Arena 还提供了一个保证直接对齐的方法:
char* Arena::AllocateAligned(size_t bytes) { const int align = (sizeof(void*) > 8) ? sizeof(void*) : 8; // 按一个指针所占大小的字节对其,最少为8 assert((align & (align-1)) == 0); // 确保对其大小时2的幂 size_t current_mod = reinterpret_cast<uintptr_t>(alloc_ptr_) & (align-1); // 相当于对align取模 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 always returned aligned memory result = AllocateFallback(bytes); } assert((reinterpret_cast<uintptr_t>(result) & (align-1)) == 0); return result; }
总结:
leveldb实现的内存分配器还是很简单的,有点简陋的感觉。相对于leveldb只用一个vector维护,c++ stl所实现的默认的内存分配器就精细多了,它按8、16、32、...... 字节大小做了多级管理,当前级不能再使用的内存还可以供下一级使用,基本很少有内存浪费,不过也因此带来了维护这个结构更高的复杂度,也需要额外保存更多的冗余信息。
leveldb 阅读笔记(1) 内存分配
标签:方便 分配器 eal blog cas 成员变量 current return size_t