目录
项目介绍+定长内存池实现 -- 高并发内存池 -- 内存池介绍+问题,定长内存池原理+代码-优快云博客
整体框架+基本实现 -- 高并发内存池 -- 内存池介绍+问题,定长内存池原理+代码-优快云博客
高并发内存池 扩展
测试
void TestConcurrentAlloc1() { Thread_Cache tc; void *p1 = tc.alloc(6); void *p2 = tc.alloc(8); // 剩1 void *p3 = tc.alloc(1); // 拿1 void *p4 = tc.alloc(7); // 剩2 void *p5 = tc.alloc(8); // 拿1 void *p6 = tc.alloc(8); // 拿1 void *p7 = tc.alloc(8); // 剩3 std::cout << p1 << std::endl; std::cout << p2 << std::endl; std::cout << p3 << std::endl; std::cout << p4 << std::endl; std::cout << p5 << std::endl; std::cout << p6 << std::endl; std::cout << p7 << std::endl; tc.dealloc(p1, 6); tc.dealloc(p2, 8); tc.dealloc(p3, 1); tc.dealloc(p4, 7); tc.dealloc(p5, 8); tc.dealloc(p6, 8); tc.dealloc(p7, 8); }
可以看到,这里我们申请出来的内存块,地址都是连续的:
- 符合我们之前维持了连续地址的特性 -- 在central cache中将切分出来的内存块尾插进链表 + thread cache 分配内存块是头删
大内存
介绍
thread cache最大支持256kb以下的内存申请
如果大于256kb呢?
- 256kb等于64页(页大小为4kb的情况下),在要申请这么多页的情况下,从thread cache开始走流程显然不可能
- 可以考虑从page cache开始
- 从这里申请,也从这里归还 -- 归还后的空间可以被合并/切分后分配给其他线程
如果再大一点,大于128页呢?
- 只能向堆申请了,page cache管不了128页以上的大内存
但依然需要借助page cache的alloc接口
- 也就是要创建一个span来管理分配的内存,因为释放的时候需要传入空间大小
- 如果只是一个指针,无法得知指向的空间是多大的
代码
#include "helper.hpp" #include "thread_cache.hpp" #include <cassert> void *concurrent_alloc(size_t size) { if (size <= helper::THREAD_CACHE_MAX_SIZE) { // 通过TLS 每个线程无锁的获取自己的专属的ThreadCache对象 if (TLS_ThreadCache == nullptr) { TLS_ThreadCache = new Thread_Cache; } return TLS_ThreadCache->alloc(size); } else // 直接走page cache流程 { int page_size = 1 << helper::PAGE_SHIFT; // int page = (size % page_size > 0) ? (size / page_size + 1) * page_size : size >> helper::PAGE_SHIFT; int page = (size + page_size - 1) >> helper::PAGE_SHIFT; std::unique_lock<std::mutex> lock(Page_Cache::get_instance()->mtx_); Span *span = Page_Cache::get_instance()->alloc(page); return helper::pageid_to_ptr(span->start_pageid_); } } void concurrent_free(void *ptr,size_t size) { if (size <= helper::THREAD_CACHE_MAX_SIZE) { assert(TLS_ThreadCache); TLS_ThreadCache->dealloc(ptr, size); } else { std::unique_lock<std::mutex> lock(Page_Cache::get_instance()->mtx_); Page_Cache::get_instance()->recycle(span); } }
Span *alloc(int page) { if (page > 128) // 直接向堆申请 { void *ptr = helper::allocate_memory(page); // 创建并初始化span Span *span = new Span; span->n_ = page; span->start_pageid_ = helper::ptr_to_pageid(ptr); page_to_span_[span->start_pageid_] = span; // 用于在释放的时候拿到span结构,来确定分配的空间大小 return span; } ...
优化释放时需要传入空间大小
介绍
因为我们要和free靠拢,free就只需要指针就可以正确释放空间
- 解决这个问题其实也很简单
本身,我们能通过指针来找到空间对应的页号
- 页号+页号->span的映射关系,我们可以找到空间对应的span结构
而每个span被切分成小块后,小块内存的大小都是一样的
- 所以,我们可以直接在span结构内定义一个字段,记录申请时的内存块大小
- 这样就不需要在释放接口处额外传入空间大小
- 可以通过指针->页号->span->空间大小
赋值
- 如果要走三层缓存,在切分对象时赋值
- 如果走page cache/堆,在申请到span后赋值
这也就是为什么向堆申请空间后,依然要转成span结构,并且要建立页号和span的映射关系
- 就是为了这里的优化
- 因为这个大小本身就是被用来判断究竟走thread cache还是page cache的释放逻辑,无论存储的是实际使用大小,还是对齐后的大小,都不影响
代码
void concurrent_free(void *ptr) { Span *span = Page_Cache::get_instance()->obj_to_span(ptr); size_t size = span->size_; if (size <= helper::THREAD_CACHE_MAX_SIZE) { assert(TLS_ThreadCache); TLS_ThreadCache->dealloc(ptr, size); } else { std::unique_lock<std::mutex> lock(Page_Cache::get_instance()->mtx_); Page_Cache::get_instance()->recycle(span); } }
central cache.hpp
Span *get_nonnull_span(std::unique_lock<std::mutex> &lock, int id, size_t size) { Span *span = span_map_[id].get_nonnull_span(); if (span != nullptr) { return span; } else // 没有span就去page cache里要 { span = alloc_page_cache(lock, id, size); span->size_ = size; // 切分成若干个对象 ... return span; } }
page_cache.hpp
Span *alloc(int page) { if (page > 128) // 直接向堆申请 { void *ptr = helper::allocate_memory(page); // 创建并初始化span Span *span = new Span; span->n_ = page; span->start_pageid_ = helper::ptr_to_pageid(ptr); span->size_ = page << helper::PAGE_SHIFT; page_to_span_[span->start_pageid_] = span; // 用于在释放的时候拿到span结构,来确定分配的空间大小 return span; } Span *span = nullptr; // 当前有对应页的span if (!span_map_[page].empty()) {