windows和Linux下如何直接向堆申请页为单位的大块内存:
前面我们规定了当申请的内存大小为 size ≤ 256*1024 也就是 256KB 的时候,就先从 central cache 申请内存,再不行就向 page cache 申请内存,还不行就向系统申请内存。
但是如果 size > 256*1024 的话,其实我们就让其直接向页缓存申请大块的页就行了,就不需要走上面的几层缓存的流程了!
// ConcurrentAlloc.h
// 向高并发内存池申请空间的接口
static void* ConcurrentAlloc(size_t size)
{
// 如果申请大小超过了256KB,则直接向页缓存申请内存
if (size > THREAD_MAX_SIZE)
{
size_t align = AlignClass::get_align(size);
size_t page = size >> PAGE_SHIFT;
// 别忘了加锁还有设为正在使用
PageCache::get_instance()->get_mutex().lock();
Span* newspan = PageCache::get_instance()->new_span(page);
newspan->_is_used = true;
PageCache::get_instance()->get_mutex().unlock();
void* ptr = (void*)(newspan->_pid << PAGE_SHIFT);
return ptr;
}
else
{
// 通过TLS机制,每个线程都能不加锁的获取到属于自己的ThreadCache对象,并且对于单个来说是全局的!
if (tls_thread_cache == nullptr)
tls_thread_cache = new ThreadCache;
return tls_thread_cache->allocate(size);
}
} 但其实 size > 256*1024 的情况还能再分,因为页缓存的哈希桶我们规定最大的页数为 128 页,但申请的内存超过了 128 页的大小也就是 128*8KB 的话,此时就只能向系统直接申请内存了!
所以我们得修改一下之前对齐数的代码,如下所示:(修改的是最后一行)
// Common.h
// 传入一个对象大小size,返回它对应的对齐数
static inline size_t get_align(size_t size)
{
// 根据字节的范围,将size以及其范围对应的对齐数交给align替我们计算对齐数
if (size <= 128) return align(size, 8);
else if (size <= 1024) return align(size, 16);
else if (size <= 8 * 1024) return align(size, 128);
else if (size <= 64 * 1024) return align(size, 1024);
else if (size <= 256 * 1024) return align(size, 8 * 1024);
// 走到这说明超过了单个线程最大申请的空间大小,则返回一页大小的对齐数即可
return align(size, 8 * 1024);
} 此时大于 128 页的情况我们就在上面会调用的 new_span() 函数中解决,就是加一个判断,如下所示:
// PageCache.cpp
// 获取一个k页大小的span
Span* PageCache::new_span(size_t k)
{
// 如果k大于128的话,则直接向系统申请内存即可
if (k > PAGELIST_NUMS - 1)
{
void* ptr = SystemAlloc(k);
Span* span = new Span;
span->_num = k;
span->_pid = (page_t)ptr >> PAGE_SHIFT;
_tables[span->_pid] = span;
return span;
}
//省略...
}Ⅱ. 释放内存
释放内存和申请内存的逻辑是一样的:
- 如果释放的内存大小是
size ≤ 256*1024也就是256KB的话,则让其走三层缓存释放内存即可。 - 如果
size > 256*1024并且页数page ≤ 128的话,则直接交给页缓存进行释放即可。 - 如果
size > 256*1024并且页数page > 128的话,则直接释放内存还给系统即可。
// ConcurrentAlloc.h
// 释放内存的接口(实际上并不会真的释放,只是还给内存池管理了)
static void ConcurrentFree(void* ptr, size_t size)
{
// 如果释放大小超过了256KB,则直接向页缓存释放内存
if (size > THREAD_MAX_SIZE)
{
Span* span = PageCache::get_instance()->get_span_from_pageID(ptr);
// 别忘了加锁
PageCache::get_instance()->get_mutex().lock();
PageCache::get_instance()->merge_memory_from_CentralCache(span);
PageCache::get_instance()->get_mutex().unlock();
}
else
{
assert(tls_thread_cache != nullptr);
tls_thread_cache->deallocate(ptr, size);
}
} 然后一样,页数的逻辑处理我们在 merge_memory_from_CentralCache() 中解决,在这之前我们先提供一下直接向堆释放内存的接口:
// Common.h
// 直接向堆中释放内存
inline static void SystemFree(void* ptr)
{
#ifdef _WIN32
VirtualFree(ptr, 0, MEM_RELEASE);
#else
// sbrk unmmap等
#endif
} 然后就可以实现 merge_memory_from_CentralCache() 剩下的逻辑了:
// PageCache.cpp
// 将CentralCache传来的span对象合并到页缓存中管理
void PageCache::merge_memory_from_CentralCache(Span* span)
{
// 大于128页的直接还给系统即可
if (span->_num > PAGELIST_NUMS - 1)
{
void* ptr = (void*)(span->_pid << PAGE_SHIFT);
SystemFree(ptr);
delete span;
return;
}
//省略...
}