怎样建设卡盟网站,米拓做的网站如何改代码,flash网站效果,怎么设计软件项目介绍
1.这个项目做的是什么#xff1f;
当前项目是实现一个高并发的内存池#xff0c;他的原型是google的一个开源项目tcmalloc#xff0c;tcmalloc全称Thread-Caching Malloc#xff0c;即线程缓存的malloc#xff0c;实现了高效的多线程内存管理#xff0c;用于替…项目介绍
1.这个项目做的是什么
当前项目是实现一个高并发的内存池他的原型是google的一个开源项目tcmalloctcmalloc全称Thread-Caching Malloc即线程缓存的malloc实现了高效的多线程内存管理用于替代系统的内存分配相关的函数malloc、free。
2.项目目标
模拟实现出一个自己的高并发内存池在多线程环境下缓解了锁竞争问题相比于malloc/free效率提高了25%左右将内存碎片保持在10%左右。
内存池介绍
池化技术
所谓“池化技术”就是程序先向系统申请过量的资源然后自己管理以备不时之需。之所以要申请过量的资源是因为每次申请该资源都有较大的开销不如提前申请好了这样使用时就会变得非常快捷大大提高程序运行效率。
在计算机中有很多使用“池”这种技术的地方除了内存池还有连接池、线程池、对象池等。以服务器上的线程池为例它的主要思想是先启动若干数量的线程让它们处于睡眠状态当接收到客户端的请求时唤醒池中某个睡眠的线程让它来处理客户端的请求当处理完这个请求线程又进入睡眠状态。
内存池
内存池是指程序预先从操作系统申请一块足够大内存此后当程序中需要申请内存的时候不是直接向操作系统申请而是直接从内存池中获取同理当程序释放内存的时候并不真正将内存返回给操作系统而是返回内存池。当程序退出(或者特定时间)时内存池才将之前申请的内存真正释放。
内存池主要解决的问题
内存池主要解决的当然是效率的问题其次如果作为系统的内存分配器的角度还需要解决一下内存碎片的问题。那么什么是内存碎片呢 定长内存池
作为程序员(C/C)我们知道申请内存使用的是mallocmalloc其实就是一个通用的大众货什么场景下都可以用但是什么场景下都可以用就意味着什么场景下都不会有很高的性能下面我们就先来设计一个定长内存池做个开胃菜当然这个定长内存池在我们后面的高并发内存池中也是有价值的所以学习他目的有两层先熟悉一下简单内存池是如何控制的第二他会作为我们后面内存池的一个基础组件。 定长内存池之所以高效是因为它可以切除固定大小的内存供线程使用。还可以回收线程释放的内存链接在自由链表中供下一次线程申请内存使用。 代码展示
#pragma once
#include iostream
#include vector
#include time.h
#include windows.h
using std::cout;
using std::endl;
// 直接去堆上按页申请空间
inline static void* SystemAlloc(size_t kpage)
{
void* ptr VirtualAlloc(0, kpage 13, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (ptr nullptr)
throw std::bad_alloc();
return ptr;
}
templateclass T
class ObjectPool
{
public:
T* New()
{
T* obj nullptr;
// 优先把还回来内存块对象再次重复利用
if (_freeList)
{
//头删
void* next *((void**)_freeList);
//将链表的第一个空间给obj使用,freeList存的就是第一个小内存的地址
obj (T*)_freeList;
_freeList next;
}
else
{
// 剩余内存不够一个对象大小时则重新开大块空间
if (_remainBytes sizeof(T))
{
_remainBytes 128 * 1024; //16页
//_memory (char*)malloc(_remainBytes);
//SystemAlloc(x)直接向系统申请内存,x表示申请的页数
_memory (char*)SystemAlloc(_remainBytes 13); //申请16页
if (_memory nullptr)
{
throw std::bad_alloc();
}
}
obj (T*)_memory;
//一个对象的大小 小于指针大小就给一个指针大小
size_t objSize sizeof(T) sizeof(void*) ?
sizeof(void*) : sizeof(T);
_memory objSize; //指针往后走一个小块空间
_remainBytes - objSize; //每用一小块空间剩余空间更新
}
// 定位new显示调用T的构造函数初始化
new(obj)T;
return obj;
}
void Delete(T* obj)
{
// 显示调用析构函数清理对象
obj-~T();
// 头插将不用的小块空间插入自由链表中
*(void**)obj _freeList; //*(void**) 解引用拿到 void*,在32/64位下大小为 4/8
_freeList obj;
}
private:
char* _memory nullptr; // 指向大块内存的指针(向系统申请的大块内存)
size_t _remainBytes 0; // 大块内存在切分过程中剩余字节数
void* _freeList nullptr; // 还回来过程中链接的自由链表的头指针
};
struct TreeNode
{
int _val;
TreeNode* _left;
TreeNode* _right;
TreeNode()
:_val(0)
, _left(nullptr)
, _right(nullptr)
{}
};
void TestObjectPool()
{
// 申请释放的轮次
const size_t Rounds 5;
// 每轮申请释放多少次
const size_t N 100000;
std::vectorTreeNode* v1;
v1.reserve(N);
size_t begin1 clock();
for (size_t j 0; j Rounds; j)
{
for (int i 0; i N; i)
{
v1.push_back(new TreeNode);
}
for (int i 0; i N; i)
{
delete v1[i];
}
v1.clear();
}
size_t end1 clock();
std::vectorTreeNode* v2;
v2.reserve(N);
ObjectPoolTreeNode TNPool;
size_t begin2 clock();
for (size_t j 0; j Rounds; j)
{
for (int i 0; i N; i)
{
v2.push_back(TNPool.New());
}
for (int i 0; i N; i)
{
TNPool.Delete(v2[i]);
}
v2.clear();
}
size_t end2 clock();
cout new cost time: end1 - begin1 endl;
cout object pool cost time: end2 - begin2 endl;
}
int main()
{
TestObjectPool();
return 0;
}
效果演示 可以看出使用定长内存池率率比使用malloc申请空间要高的多。
相关视频推荐
200行代码实现slab开启内存池的内存管理准备linux环境
90分钟了解Linux内存架构numa的优势slab的实现vmalloc原理
5种内存泄漏检测方式让你重新理解C内存管理 免费学习地址C/CLinux服务器开发/后台架构师
需要C/C Linux服务器架构师学习资料加qun579733396获取资料包括C/CLinuxgolang技术NginxZeroMQMySQLRedisfastdfsMongoDBZK流媒体CDNP2PK8SDockerTCP/IP协程DPDKffmpeg等免费分享 高并发内存池整体框架设计
现代很多的开发环境都是多核多线程在申请内存的场景下必然存在激烈的锁竞争问题。malloc本身其实已经很优秀那么我们项目的原型tcmalloc就是在多线程高并发的场景下更胜一筹所以这次我们实现的内存池需要考虑以下几方面的问题。
1. 性能问题。
2. 多线程环境下锁竞争问题。
3. 内存碎片问题。
thread cache线程缓存是每个线程独有的用于小于256KB的内存的分配线程从这里申请内存不需要加锁每个线程独享一个cache这也就是这个并发线程池高效的地方。
central cache中心缓存是所有线程所共享thread cache是按需从central cache中获取的对象。central cache合适的时机回收thread cache中的对象避免一个线程占用了太多的内存而其他线程的内存吃紧达到内存分配在多个线程中更均衡的按需调度的目的。central cache是存在竞争的所以从这里取内存对象是需要加锁首先这里用的是桶锁其次只有thread cache的没有内存对象时才会找central cache所以这里竞争不会很激烈。
page cache页缓存是在central cache缓存上面的一层缓存存储的内存是以页为单位存储及分配的central cache没有内存对象时从page cache分配出一定数量的page并切割成定长大小的小块内存分配给central cache。当一个span的几个跨度页的对象都回收以后page cache会回收central cache满足条件的span对象并且合并相邻的页组成更大的页缓解内存碎片的问题。 高并发内存池–thread cache
thread cache是哈希桶结构每个桶是一个按桶位置映射大小的内存块对象的自由链表。每个线程都会有一个thread cache对象这样每个线程在这里获取对象和释放对象时是无锁的。 申请内存
当内存申请size256KB先获取到线程本地存储的thread cache对象计算size映射的哈希桶自由链表下标i。如果自由链表_freeLists[i]中有对象则直接Pop一个内存对象返回。如果_freeLists[i]中没有对象时则批量从central cache中获取一定数量的对象插入到自由链表并返回一个对象。
释放内存
4. 当释放内存小于256k时将内存释放回thread cache计算size映射自由链表桶位置i将对象Push到_freeLists[i]。
5. 当链表的长度过长则回收一部分内存对象到central cache。
如何保证线程可以创建属于自己的thread cache?
线程局部存储TLS是一种变量的存储方法这个变量在它所在的线程内是全局可访问的但是不能被其他线程访问到这样就保持了数据的线程独立性。而熟知的全局变量是所有线程都可以访问的这样就不可避免需要锁来控制增加了控制成本和代码复杂度。
thread cache代码框架
#pragma once
#include Common.h
class ThreadCache
{
public:
// 申请和释放内存对象
void* Allocate(size_t size);
void Deallocate(void* ptr, size_t size);
// 从中心缓存获取对象
void* FetchFromCentralCache(size_t index, size_t size);
// 释放对象时链表过长时回收内存回到中心缓存
void ListTooLong(FreeList list, size_t size);
private:
FreeList _freeLists[NFREELIST];
};
// TLS thread local storage线程本地存储每个线程都有自己的线程本地存储
//有了TLS,线程来访问就不需要加锁了被static修饰只在当前文件可见
static _declspec(thread) ThreadCache* pTLSThreadCache nullptr;
// 管理切分好的小对象的自由链表
class FreeList
{
public:
void Push(void* obj)
{
assert(obj);
// 头插
//*(void**)obj _freeList; //*(void**)obj取obj头上4个或8个字节指向_freeList
NextObj(obj) _freeList;
_freeList obj;
_size;
}
void PushRange(void* start, void* end, size_t n)
{
NextObj(end) _freeList;
_freeList start;
// 测试验证条件断点
/*int i 0;
void* cur start;
while (cur)
{
cur NextObj(cur);
i;
}
if (n ! i)
{
int x 0;
}*/
_size n;
}
void PopRange(void* start, void* end, size_t n)
{
assert(n _size);
start _freeList;
end start;
for (size_t i 0; i n - 1; i)
{
end NextObj(end);
}
_freeList NextObj(end);
NextObj(end) nullptr;
_size - n;
}
void* Pop()
{
assert(_freeList);
// 头删
void* obj _freeList;
_freeList NextObj(obj);
--_size;
return obj;
}
bool Empty()
{
return _freeList nullptr;
}
size_t MaxSize()
{
return _maxSize;
}
size_t Size()
{
return _size;
}
private:
void* _freeList nullptr;
size_t _maxSize 1;
size_t _size 0;
};
自由链表的哈希桶跟对象大小的映射关系
// 计算对象大小的对齐映射规则
class SizeClass
{
public:
// 整体控制在最多10%左右的内碎片浪费
// [1,128] 8byte对齐 freelist[0,16)
//假设需要129字节会分配给你144字节给你就有15字节的浪费 15/1440.104
// [1281,1024] 16byte对齐 freelist[16,72)
//假设需要1025个字节会分配给你1152字节给你就有127字节的浪费 127/11520.11
// [10241,8*1024] 128byte对齐 freelist[72,128)
// [8*10241,64*1024] 1024byte对齐 freelist[128,184)
// [64*10241,256*1024] 8*1024byte对齐 freelist[184,208)
/*size_t _RoundUp(size_t size, size_t alignNum)
{
size_t alignSize;
if (size % alignNum ! 0)
{
alignSize (size / alignNum 1)*alignNum;
}
else
{
alignSize size;
}
return alignSize;
}*/
// 1-8
static inline size_t _RoundUp(size_t bytes, size_t alignNum)
{
return ((bytes alignNum - 1) ~(alignNum - 1));
}
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 //256KB
{
return _RoundUp(size, 1PAGE_SHIFT);
}
}
static inline size_t _Index(size_t bytes, size_t align_shift)
{
return ((bytes (1 align_shift) - 1) align_shift) - 1;
}
// 计算映射的哪一个自由链表桶
static inline size_t Index(size_t bytes)
{
assert(bytes MAX_BYTES);
// 每个区间有多少个链
static int group_array[4] { 16, 56, 56, 56 };
if (bytes 128){
return _Index(bytes, 3);
}
else if (bytes 1024){
return _Index(bytes - 128, 4) group_array[0];
}
else if (bytes 8 * 1024){
return _Index(bytes - 1024, 7) group_array[1] group_array[0];
}
else if (bytes 64 * 1024){
return _Index(bytes - 8 * 1024, 10) group_array[2] group_array[1] group_array[0];
}
else if (bytes 256 * 1024){
return _Index(bytes - 64 * 1024, 13) group_array[3] group_array[2] group_array[1] group_array[0];
}
else{
assert(false);
}
return -1;
}