目录
第一节:函数修改
1-1.ConcurrentAlloc.h
1-2.Common.h
1-3.PageCache.cpp
第二节:测试
第三节:结语
大内存的思路是将其以一页为对齐数,申请一个为切分的span,这种span在pc就有,所以直接到pc中申请,释放时也直接还给pc,不需要经过tc和cc。
第一节:函数修改
1-1.ConcurrentAlloc.h
该文件的函数ConcurrentAllocate增加处理大内存的代码:
// 线程申请私有的tc+申请size字节内存
static void* ConcurrentAlloc(size_t size) {
if (pTlsTC == nullptr) {
static FixedLengthMemoryPool<ThreadCache> cmpool;
pTlsTC = cmpool.New();
}
//----------------------------------------------------------------
// 处理大内存
if (size > MAX_BYTES) {
size_t alignSize = SizeClass::RoundUp(size);
size_t npage = alignSize >> PAGE_SHIFT; // 得到页数
// 获得足够大的span
PageCache::GetInst()->_mtx.lock();
Span* span = PageCache::GetInst()->NewSpan(npage);
span->_objSize = alignSize;
PageCache::GetInst()->_mtx.unlock();
void* ptr = (void*)((span->_pageId) << PAGE_SHIFT);
return ptr;
}
//----------------------------------------------------------------
return pTlsTC->Allocate(size);
}
函数ConcurrentFree新增如下代码:
static void ConcurrentFree(void* ptr) {
assert(pTlsTC);
Span* span = PageCache::GetInst()->MapObjToSpan(ptr);
size_t size = span->_objSize;
//----------------------------------------------------------------
// 释放大内存
if (size > MAX_BYTES) {
PageCache::GetInst()->_mtx.lock();
// 直接将该span还给pc
PageCache::GetInst()->ReleaseSpanToPageCache(span);
PageCache::GetInst()->_mtx.unlock();
return;
}
//----------------------------------------------------------------
pTlsTC->Deallocate(ptr, size);
}
1-2.Common.h
需要修改的是SizeClass中的RoundUp函数,之前对于大内存的处理是报错,需要修改成以一页为大小进行对齐:
// size对齐后的字节数
static 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 { // 大于MAX_BYTES的大块内存
return _RoundUp(size, 1 << PAGE_SHIFT); // 修改处
}
}
再在Common.h中添加一个名为SystemFree的函数,它的作用是如果还回来的内存大于128页,那么pc放不下,直接还给堆:
// 向堆还空间
static void SystemFree(void* ptr) {
VirtualFree(ptr, 0, MEM_RELEASE);
}
1-3.PageCache.cpp
修改NewSpan函数,如果申请的内存大于128页,那么直接向堆获取,直接返回:
Span* PageCache::NewSpan(size_t npage) {
assert(npage > 0);
//----------------------------------------------------------------
// 大于128页直接从堆申请
if (npage > NPAGE - 1) {
void* ptr = SystemAlloc(npage);
Span* span = _fLengthMemPool.New();
span->_pageId = (PAGE_ID)ptr >> PAGE_SHIFT;
span->_n = npage;
_idSpanMap.set(span->_pageId,span);
return span;
}
//----------------------------------------------------------------
// 其他内容不变......
}
修改ReleaseSpanToPageCache函数,如果释放的内存大于128页就调用SystemFree函数将其直接还给堆:
void PageCache::ReleaseSpanToPageCache(Span* span) {
//----------------------------------------------------------------
// 大于128页直接还给堆
if (span->_n > NPAGE - 1) {
void* ptr = (void*)(span->_pageId << PAGE_SHIFT);
SystemFree(ptr);
_fLengthMemPool.Delete(span);
return;
}
//----------------------------------------------------------------
// 其他内容不变......
}
这样代码就修改完毕了。
第二节:测试
申请各种大小的大内存,只要没报错代码就可以了:
std::atomic<size_t> poolCostTime = 0;
std::atomic<size_t> mallocCostTime = 0;
size_t nTimes = 10;
void _ConWithMalloc() {
std::vector<void*> vPtr1;
std::vector<void*> vPtr2;
vPtr1.reserve(nTimes);
vPtr2.reserve(nTimes);
size_t begin1 = clock();
for (size_t i = 0; i < nTimes; i++) {
vPtr1.push_back(ConcurrentAlloc(8*129 << PAGE_SHIFT)); // 修改其中的值测试不同大小的内存申请
}
size_t end1 = clock();
size_t begin2 = clock();
for (size_t i = 0; i < nTimes; i++) {
ConcurrentFree(vPtr1[i]);
}
size_t end2 = clock();
poolCostTime += (end1 - begin1) + (end2 - begin2);
begin1 = clock();
for (size_t i = 0; i < nTimes; i++) {
vPtr2.push_back(malloc(8 * 56 << PAGE_SHIFT));
}
end1 = clock();
begin2 = clock();
for (size_t i = 0; i < nTimes; i++) {
free(vPtr2[i]);
}
end2 = clock();
mallocCostTime += (end1 - begin1) + (end2 - begin2);
}
void ConWithMalloc(size_t nWorks) {
// nWorks个线程,每个线程执行nTimes次申请和释放内存
std::vector<std::thread> vThread(nWorks);
for (size_t k = 0; k < nWorks; k++) {
vThread[k] = std::thread(_ConWithMalloc);
}
for (auto& t : vThread)
t.join();
std::cout << poolCostTime << " " << mallocCostTime << std::endl;
}
int main() {
for (size_t i = 0; i < 100; i++)
ConWithMalloc(4);
return 0;
}
第三节:结语
这样一个简化的高并发内存池就完成了,这是tcmalloc的简化版本,学习项目的主要目的还是学习大佬的代码思路。
这是我学习的第一个项目,这种内存相关的bug真的是不好找,有时候一个bug要找几个小时,但是我仍然还是坚持下来了。
我学习到了找bug的技巧、大佬的代码思路,这个项目完成后,我要需要马不停蹄的学习下一个项目了。
路漫漫其修远兮,吾将上下而求索。