为了高效回收和利用内存空间而创建的类
class Arena {
public:
Arena();
Arena(const Arena&) = delete;
Arena& operator=(const Arena&) = delete;
~Arena();
// Return a pointer to a newly allocated memory block of "bytes" bytes.
char* Allocate(size_t bytes);
// Allocate memory with the normal alignment guarantees provided by malloc.
char* AllocateAligned(size_t bytes);
// Returns an estimate of the total memory usage of data allocated
// by the arena.
size_t MemoryUsage() const {
return memory_usage_.load(std::memory_order_relaxed);
}
private:
char* AllocateFallback(size_t bytes);
char* AllocateNewBlock(size_t block_bytes);
// Allocation state
char* alloc_ptr_;
size_t alloc_bytes_remaining_;
// Array of new[] allocated memory blocks
std::vector<char*> blocks_;
// Total memory usage of the arena.
// // TODO(costan): This member is accessed via atomics, but the others are
// accessed without any locking. Is this OK?
std::atomic<size_t> memory_usage_;
};blocks_中的每个元素都是使用new[]申请的内存块memory_usages_使用原子类型记录的内存使用量,可以不用加锁。alloc_ptr_和alloc_bytes_remaining_:则是用于内存分配的状态,来看一下下面的内存分配代码就有比较清晰的认识了
inline char* Arena::Allocate(size_t bytes) {
// 为了避免混乱,不允许申请的bytes=0.否则可能有多个指向同一地址的指针
assert(bytes > 0);
if (bytes <= alloc_bytes_remaining_) {
char* result = alloc_ptr_;
alloc_ptr_ += bytes;
alloc_bytes_remaining_ -= bytes;
return result;
}
return AllocateFallback(bytes);
}可以看到当申请的内存小于当前剩余内存的时候,会直接将当前内存拆分一块出去,而不是重新申请一块内存空间。只有当剩余的空间不足的时候才会申请新的内存空间。
进入AllocateFallback也可以看到,申请新的内存空间的时候也不是原原本本按照给定的大小进行申请,而是根据申请的大小进行不同的处理
static const int kBlockSize = 4096;
char* Arena::AllocateFallback(size_t bytes) {
// 当申请的字节大小超过kBlockSize的四分之一的时候,按照给定的大小分配,这样来避免空间的浪费。
if (bytes > kBlockSize / 4) {
return result;
}
// 申请的字节比较少的情况时候,直接申请4k大小的内存块。这个块可以被分配成很多小内存,以提高内存的使用率以及减少内存分配的次数,提高效率
alloc_ptr_ = AllocateNewBlock(kBlockSize);
alloc_bytes_remaining_ = kBlockSize;
char* result = alloc_ptr_;
alloc_ptr_ += bytes;
alloc_bytes_remaining_ -= bytes;
return result;
}至于实际执行内存分配的AllocateNewBlock,也就是调用new关键字进行分配
char* Arena::AllocateNewBlock(size_t block_bytes) {
char* result = new char[block_bytes];
blocks_.push_back(result);
// 把指针的大小也统计到内存使用量上了
memory_usage_.fetch_add(block_bytes + sizeof(char*),
std::memory_order_relaxed);
return result;
}Arena将所有申请的内存块都保存在了blocks_数组当中,在Arena对象释放的时候再统一释放掉这个内存
Arena::~Arena() {
for (size_t i = 0; i < blocks_.size(); i++) {
delete[] blocks_[i];
}
}不要遗漏了还有AllocateAligned这个函数,主要是为了保证申请的内存块是内存对齐的,新分配的块自然不需要考虑,都是内存对齐的。但是将申请的大块拆分为小块的时候就需要注意了内存对齐了。
char* Arena::AllocateAligned(size_t bytes) {
// 申请的指针需要按照align对齐
const int align = (sizeof(void*) > 8) ? sizeof(void*) : 8;
static_assert((align & (align - 1)) == 0,
"Pointer size should be a power of 2");
// 对align取模的位运算
size_t current_mod = reinterpret_cast<uintptr_t>(alloc_ptr_) & (align - 1);
// 想要对齐需要补足的字节
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;
}