我们的内存池已经完成了申请内存和释放内存的功能但是还有几个地方没有处理完善,主要有以下几个方面:
- 大于256KB的内存申请
- 内存池的内部要脱离new
- 释放对象时的细节
大于256KB的内存处理
申请
在ConcurrentAlloc.h文件中,申请内存的时候要分两种情况
- 小于256kb走thread cache
- 大于256kb走page cache
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| static void* ConcurrentAlloc(size_t size) { if (size > MAX_BYTES) {
size_t alignSize = SizeClass::RoundUp(size); size_t kpage = alignSize >> PAGE_SHITF; PageCache::GetInstance()->_pageMtx.lock(); Span* span = PageCache::GetInstance()->NewSpan(alignSize); PageCache::GetInstance()->_pageMtx.unlock();
return (void*)(span->_pageId << PAGE_SHITF); } else { if (pTLSThreadCache == nullptr) { pTLSThreadCache = new ThreadCache; } return pTLSThreadCache->Allocate(size); } }
|
我们SizeClass对齐的逻辑,并没有处理大于256KB的内存,我们需要增加这一部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| static inline size_t RoundUp(size_t size) { if (size <= 128) { return _RoundUp(size, 8); } else if (size <= 1024) { return _RoundUp(size, 16);
} else if (size <= 8 * 1024) { return _RoundUp(size, 128);
} else if (size <= 64 * 1024) { return _RoundUp(size, 1024);
} else if (size <= 256 * 1024) { return _RoundUp(size, 8*1024);
} else { return _RoundUp(size, 1 << PAGE_SHITF); } }
|
但是这个大于256kb的让page cache申请,还是存在一些问题,因为page cache最多提供128页的内存,如果申请的内存大于128页,则还是需要进行处理,这部分,我们在page cache中进行处理。
PageCache::NewSpan函数中,增加申请大于128页的span的逻辑
1 2 3 4 5 6 7 8 9 10 11 12
|
if (k > NPAGES - 1) { void* ptr = SystemAlloc(k); Span* span = new Span; span->_pageId = (PAGE_ID)ptr >> PAGE_SHITF; span->_n = k;
_idSpanMap[span->_pageId] = span; return span; }
|
释放
大于256kb的内存,释放时,同样不能走thread cache层回收,要有单独的回收逻辑,我们还是通过pagecache层回收
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| static void ConcurrentFree(void* ptr, size_t size) {
if (size > MAX_BYTES) { Span* span = PageCache::GetInstance()->MapObjectToSpan(ptr);
PageCache::GetInstance()->_pageMtx.lock(); PageCache::GetInstance()->ReleaseSpanToPageCache(span); PageCache::GetInstance()->_pageMtx.unlock();
} else { assert(pTLSThreadCache); pTLSThreadCache->Deallocate(ptr, size); }
}
|
同样,在pagecache层中还要分为,这个内存是大于128页还是小于128页,小于128直接走Page cache层的回收逻辑,大于128页的通过系统调用回收
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| void PageCache::ReleaseSpanToPageCache(Span* span) { if (span->_n > NPAGES - 1) { void* ptr = (void*)(span->_pageId << PAGE_SHITF); SystemFree(ptr); delete span; return; } while (1) { PAGE_ID prevId = span->_pageId - 1; auto ret = _idSpanMap.find(prevId); if (ret == _idSpanMap.end()) { break; }
Span* prevSpan = ret->second; if (prevSpan->_isUse == true) { break; }
if (prevSpan->_n + span->_n > NPAGES - 1) { break; }
span->_pageId = prevSpan->_pageId; span->_n += prevSpan->_n;
_spanLists[prevSpan->_n].Erase(prevSpan); delete prevSpan; }
while (1) { PAGE_ID nextID = span->_pageId + span->_n; auto ret = _idSpanMap.find(nextID); if (ret == _idSpanMap.end()) { break; }
Span* nextSpan = ret->second; if (nextSpan->_isUse == true) { break; }
if (nextSpan->_n + span->_n > NPAGES - 1) { break; }
span->_n += nextSpan->_n;
_spanLists[nextSpan->_n].Erase(nextSpan); delete nextSpan; }
_spanLists[span->_n].PushFront(span); span->_isUse = false;
_idSpanMap[span->_pageId] = span; _idSpanMap[span->_pageId + span->_n - 1] = span; }
|
这里封装的了Windows系统的VirtualFree函数,进行大于128页的内存释放
1 2 3 4 5 6 7 8 9
| inline static void SystemFree(void* ptr) { #ifdef _WIN32 VirtualFree(ptr, 0, MEM_RELEASE); #else #endif }
|
使用定长内存池配合脱离new
我们的内存池期望的是在内部不使用malloc函数申请内存,走自己的一套申请释放体系,但是目前我们的代码中,还是有不少地方(new span的对象)使用了new,为了完全脱离new,我们可以配合之前的定长内存池。
申请
因为span对象都是在page cache层new出来的,我们可以在page cache层增加一个定长内存池对象
1
| ObjectPool<Span> _spanPool;
|
然后我们在new Span的场景下,替换成定长内存池的申请
1 2 3 4 5
| Span* span = new Span;
Span* span = _spanPool.New();
|
在线程申请创建thread cache时,也是通过new进行申请的,同样进行替换
1 2 3 4 5 6
| if (pTLSThreadCache == nullptr) { static ObjectPool<ThreadCache> tcPool; pTLSThreadCache = tcPool.New(); }
|
释放
对应的delete也要替换成定长内存池的Delete
释放内存优化为不传对象大小
我们用free时,只需要传入一个指针即可,但是我们目前的内存池释放内存,不仅要传入指针,还需要传入待释放内存的大小,我们也需要优化为不传入对象的大小
我们可以在span对象中增加成员:ObjSize 用来记录span中每个对象的大小
那我们的释放内存的函数就可以改为下边的写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| static void ConcurrentFree(void* ptr) { Span* span = PageCache::GetInstance()->MapObjectToSpan(ptr); size_t size = span->_ObjSize;
if (size > MAX_BYTES) { PageCache::GetInstance()->_pageMtx.lock(); PageCache::GetInstance()->ReleaseSpanToPageCache(span); PageCache::GetInstance()->_pageMtx.unlock();
} else { assert(pTLSThreadCache); pTLSThreadCache->Deallocate(ptr, size); } }
|
现在的问题就变成了,ObjSize的记录问题了,我们整个内存池中的span对象都是从Page cache层出来的,所以在调用NewSpan的时候,要将切分的对象的大小记录下来,我们申请小于256kb的对象时,都走的thread cache->central cache->page cache,另一种情况是大于256kb直接找page cache,这两个地方都需要记录ObjSize
1、在Central层调用NewSpan后,使得span->ObjSize=size
1 2 3 4
| Span* span = PageCache::GetInstance()->NewSpan(SizeClass::NumMovePage(size)); span->_ObjSize = size;
|
2、CurrentAlloc中,申请大于256KB的对象
1 2
| Span* span = PageCache::GetInstance()->NewSpan(alignSize); span->_ObjSize = alignSize;
|
STL线程安全问题
我们通过unordered_map,建立了PAGE_ID和Span* 的联系,PageCache层,有读写操作,但是我们在使用PageCache的NewSpan函数时,都会加锁,但是CentralCache层和ConcurrentFree函数中会有读操作,而且并未加锁,这样,可能有线程正在改,就有线程读取,会有线程安全的问题
我们在MapObjectToSpan函数中加锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| Span* PageCache::MapObjectToSpan(void* obj) { PAGE_ID id = ((PAGE_ID)obj >> PAGE_SHITF); std::unique_lock<std::mutex> lock(_pageMtx); if (_idSpanMap.find(id) != _idSpanMap.end()) { return _idSpanMap[id]; } else { assert(false); return nullptr; } }
|