经典的arena内存池实现-levelDB的内存池实现
admin
2024-02-29 22:32:42
0

Arena实现

arena可以说是解决内存碎片的利器,虽然有很多前辈说,要相信malloc的实现,你能想到的那些问题在设计Malloc的时候肯定都考虑到了。是的你可以相信malloc的实现,但是你不能对你自己有过分的自信,在功能比较复杂,特别是工作量比较大的时候,你不能保证你申请的每块内存都得到有效的释放,这个时候就不可避免的出现内存泄露。

arena解决的问题

  • 频繁分配内存,造成内存碎片化

  • 申请之后忘记释放,造成内存泄露

C语言接口与设计一书中,详细说明了Arena的设计,levelDB中可以说是简化了Arena的复杂度,只实现了内存的申请,没有实现内存的释放。

实现细节:

  1. 如果申请内存大于1024字节,直接申请一块指定大小的内存(默认4096),并将内存地址返回

  2. 如果申请的内存小于1024字节,则按照4096大小进行申请,返回指定大小内存,并记录剩余内存的大小,方便下次申请使用

适用场景:

Arena在事件处理、流水线处理、请求类型处理中有具有无可无可比拟的优势,事件开始,创建arena,中间过程无论那需要内存,只管申请,申请之后不用担心释放的事情,等到事件结束之后,只需要释放arena句柄就行了,即避免了内存碎片,又避免了内存泄露,同时也减轻了程序员的负担。

下面是levelDB中Arena实现的头文件

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 statechar *alloc_ptr_;size_t alloc_bytes_remaining_;// Array of new[] allocated memory blocksstd::vector 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 memory_usage_;
};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);
}

可以看到,只是简单的实现了内存的申请,内存释放需要依赖析枸arena来一次性析枸,但这样已经足够内存数据库这种场景使用了。

下面来看下函数的具体实现,第一个就是头文件里面实现的Allocate函数

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).// 如果允许申请0字节的内存,会造成很多换乱的问题,因此内部接口中我们禁止这种使用方式,bytes要确保大于0assert(bytes > 0);// 如果上次申请的内存还够用if (bytes <= alloc_bytes_remaining_) {// 记录当前指针地址char *result = alloc_ptr_;// 将当前指针向后移动bytesalloc_ptr_ += bytes;// 剩余的内存大小在原有的基础上要减少 bytesalloc_bytes_remaining_ -= bytes;// 将地址放回return result;}// 如果上次申请剩余的内存不够,或者第一次进来申请内存,就新申请内存return AllocateFallback(bytes);
}

当申请的内存大于kBlockSize直接申请指定大小的内存,如果小于kBlockSize就一次申请kBlockSize大小的内存,将alloc_ptr_指向本次使用内存的结尾并使用alloc_bytes_remaining_记录剩余可用内存大小

static const int kBlockSize = 4096;

在创建Arena对象的时候,构造函数会将当前申请内存的指针赋值为空,剩余可用内存大小也设置为空,已使用的内存也设置为空,这些值的设置也符合我们常用的规范

Arena::Arena(): alloc_ptr_(nullptr), alloc_bytes_remaining_(0), memory_usage_(0) {}

在每次需要新申请内存的时候,我们都会把新申请的内存插入到blocks_中,这样在释放对象的析构函数中只需要将所有blocks_中的插入的对象进行释放即可将整个Arena存在期间申请的内存一次性释放干净。

Arena::~Arena() {for (auto & block : blocks_) {delete[] block;}
}

从上面的函数可以看出来,Allocate函数内部真正申请内存调用的是AllocateFallback函数,具体的实现如下:

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.// 申请的原则// 如果申请的内存大于指定block size的四分之一,就按照指定内存大小进行申请char *result = AllocateNewBlock(bytes);return result;}// We waste the remaining space in the current block.// 如果需要的大小小于1024字节,那么按照个4096字节大小申请,alloc_ptr_ = AllocateNewBlock(kBlockSize);// 申请之后将remaining大小修改为申请的大小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];// 将每次new出来的指针方能如到blocks中blocks_.push_back(result);// 已经使用的内存,是一个指针的大小和指定内存大小的和memory_usage_.fetch_add(block_bytes + sizeof(char *),std::memory_order_relaxed);return result;
}
char *Arena::AllocateAligned(size_t bytes) {// 如果当前系统指针大于8字节,就按照指针大小进行对齐,如果不是就按照8字节对齐const int align = (sizeof(void *) > 8) ? sizeof(void *) : 8;// 确保对齐字节大小是2的次方static_assert((align & (align - 1)) == 0,"Pointer size should be a power of 2");// 看当前的alloc_ptr_是否是8字节对齐size_t current_mod = reinterpret_cast(alloc_ptr_) & (align - 1);// 如果 alloc_ptr_是8字节对齐,那么current_mod会等于0,slop也会是0,,如果slop是alloc_ptr_前进多少能够8字节对齐的位置size_t slop = (current_mod == 0 ? 0 : align - current_mod);// 实际需要自己的大小为slop和需要申请内存大小的和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 memoryresult = AllocateFallback(bytes);}// 确保申请内存的指针是8字节对齐assert((reinterpret_cast(result) & (align - 1)) == 0);return result;
}

相关内容

热门资讯

佛得角主帅:我们要证明配得上世... 新华社消息,美加墨世界杯H组第二轮乌拉圭队对阵佛得角队的比赛将于21日打响。佛得角队主帅布比斯塔20...
本周天孚通信、大金重工等8家企... 4月11日消息,据港交所披露,4月6日至10日,共8家企业向港交所递交招股书,包括CPO龙头天孚通信...
抖音:有用户将部分商家品牌名等... 4月11日消息,抖音发文表示,近期,平台发现有用户搬运站外谣言信息,将部分商家品牌名、logo设计、...
内蒙古自贸试验区获批启航,三大... 4月11日消息,内蒙古召开中国(内蒙古)自由贸易试验区新闻发布会,聚焦自贸试验区建设核心内容,明确发...
“大空头”伯里放话:Palan... 4月11日消息,美国知名“空头”迈克尔·伯里(Michael Burry)周五在其付费博客上发文称,...
极氪焕新7系正式上市,001五... 4月11日消息,极氪宣布,旗下焕新7系 焕新极氪007与焕新极氪007GT 正式上市。其中,焕新极氪...
库里,是李宁的正确答案吗? 库... 出品|虎嗅商业消费组作者|李佳琪编辑|苗正卿题图|库里四届NBA总冠军、两届常规赛MVP、改变篮球打...
湖南黄金迎来密集新老交接:56... 湖南省属国有矿业龙头湖南黄金(002155.SZ)正在密集进行核心岗位的人事新老交接。继4月董事长王...
“太空算力专业委员会”启动成员... 4月11日消息,中国业界首个太空算力产业协同平台“太空算力专业委员会”正式面向全国征集首批成员单位。...
3·15晚会曝光“万能神药”涉... 4月11日消息,从天津市市场监管部门了解到,被央视总台3·15晚会曝光的灏麟(天津)生物科技有限公司...