内存池介绍

Posted ych9527

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了内存池介绍相关的知识,希望对你有一定的参考价值。

1.什么是内存池

1.1池化技术

池是计算机中常用的一种设计模式,其特点是将资源提前申请好,放在’‘资源池’'之中由程序自己控制资源的使用,这样减少了与内核的交互

因为资源的申请是需要通过内核来完成的,与内核交互的频率越高, 程序的效率就越低,提前将资源申请出来,使用资源的时候不需要再向内核申请,直接就可以使用,在一定程度上提高了程序的效率

常见的池化技术有线程池,内存池等等

1.2内存池

通常我们使用内存是直接通过new/malloc直接申请,通过delete/free直接释放。

而内存池则是提前向系统申请一块较大的内存放在’池子‘中,当程序需要内存的时候,自动去池子之中获取内存,当使用完毕之后,再将内存释放回去,这样可以达到内存重复利用的目的

2.为什么需要内存池

  • 解决内存碎片问题(外碎片)
  • 减少与内核的交互,提高了申请的效率

3.内存池的发展

3.1早期内存池

image-20210701145722269

  • 优点:实现简单

  • 缺点:分配时搜索合适的内存块效率低,释放回归内存后归并消耗大,实际中不实用

3.2定长内存池分配器

  • 不会随意切割内存块出来,而是只会切割某些固定大小的内存块只解决固定大小的内存块的申请和释放,如果给vector用,string对象用等等。固定的大小还回来后,再用链表连接管理起来,这个链表就叫做自由链表(FreeList)

image-20210701151449489

  • 优点:可以解决特定场景下的内存分配分体
  • 缺点:功能单一,只能解决定长的内存需求,比如vector需要一个内存池,map也需要一个内存池,非常麻烦

3.3哈希映射的FreeList内存池

  • 根据定长内存池进行优化,将可以固定分配的内存多样化,通过哈希映射起来

  • 比如STL六大组件使用的内存池切割的方式就有8、16、24、32…128多种固定字节的内存块

  • (sgi、linux下)STL中分为一级空间配置器和二级空间配置器,二级空间配置器就是内存池,最大可以切割的内存为128,超过128就使用一级空间配置器(其实就是调用malloc和free)

  • 不是每个内存的大小都挂一个桶,所以会造成一定的内存浪费,比如要1-7字节分配的都是8字节

  • 当给的内存比实际要的内存大的时候(不是每个内存都挂一个桶),就会造成内存浪费,这个就叫做内碎片,外碎片是导致申请不出来空间,内碎片是空间浪费问题

  • 申请固定内存块时,也不是一次只切一块下来,每次会切多块下来

image-20210701154311075

  • 优点:根据定长内存池改造,可以在一定程度上解决长度问题(多种固定长度)

  • 缺点:

    1.多线程场景下,都在使用STL容器,在申请内存的时候需要加锁,会导致效率低

    2.大于128的内存就是malloc和free

    3.也会存在内存碎片问题(外碎片),因为每次申请的memory大块内存会被切割分小,这些不能再组成大内存,因此当需要大块内存的时候,可能会存在内存碎片问题

4.实现一个定长内存池

注意事项:

  • 自由链表中,用当前指针所指向的空间的前面几个字节,存储下一断空间的指针,由于不清楚指针类型的大小,所以采用特殊方法处理
*((int*)obj)=(int)_FreeLeft //取指针指向空间的前四个字节,存储下一个指针,但是在64位下,指针的大小是8个字节,就会出问题
    
//*((void**)obj) = _FreeList;//void**解引用出来 -> void*在32位就是4字节大小,64位就是8字节大小
  • 当给定的类型,小于当前系统下指针的大小的时候,用其空间大小是无法存储下指针的,也需要进行特殊处理
//_memory += sizeof(T);//假如T为char,此时就会有问题,只给一个字节,释放的时候无法存储指针

static size_t GetObjSize()
	{
		if (sizeof(T) < sizeof(T*))//类型比指针小
			return sizeof(T*);

		return sizeof(T);
	}
  • memory有可能有越界问题,因此需要记录剩余大块的内存的大小
int _LeftSize = 0;//申请的内存剩余大小,防止越界
  • 内存池的释放:定义一个链式结构,将每次向系统申请的内存的起始地址保存起来,需要释放内存的时候,依次释放链表中保存的内存

  • 实现代码:

<common.h>
#pragma once

#include <iostream>
using std::cout;
using std::cin;
using std::endl;
#include <exception>
#include <time.h>



#pragma once
#include "common.h"

template<class T>
class ObjectPool
{
private:
	size_t GetObjSize()
	{
		if (sizeof(T) < sizeof(T*))//类型比指针小
			return sizeof(T*);

		return sizeof(T);
	}

	void* &NextObj(void *obj)//从自由链表更新指针
	{
		return (*(void**)obj);//取前面8个字节的内容
	}

	struct BlockNode//存储大块内存的起始地址
	{
		BlockNode(char *memory = nullptr)
		:_memory(memory)
		,_next(nullptr)
		{}

		~BlockNode()
		{
			free (_memory);
		}

		char *_memory;
		BlockNode* _next;
	};

	void Add()//保存新开辟的大块内存
	{
		BlockNode* node = new BlockNode(_memory);
		node->_next = BlockHead->_next;
		BlockHead->_next = node;
	}

public:
	T *New()
	{
		T *obj = nullptr;


		if (_FreeList)//不为空,在自由链表中或者
		{
			obj = (T*)_FreeList;
			_FreeList = NextObj(_FreeList);//往下走一步
		}
		else
		{	
			if (_LeftSize < sizeof(T))//空间不够了
			{
				_LeftSize = 1024 * 200 * _count;
				_memory = (char*)malloc(_LeftSize);
				_count++;

				if (_memory==nullptr)//申请失败抛异常
					throw std::bad_alloc();

				//将新开辟的内存添加至保存大块内存起始地址的结构体中
				Add();
			}
			obj = (T*)_memory;

			//_memory += sizeof(T);//假如T为char,此时就会有问题,只给一个字节,释放的时候无法存储指针
			//_LeftSize -= sizeof(T);

			_memory += _ObjSize;
			_LeftSize -= _ObjSize;

		}

		new (obj)T;//定位new,初始化空间
		return obj;
	}

	void Delete(T *obj)//释放内存,将内存连接到自由链表之中
	{
		//头插 -> 用自由链表的前面的指针的前面的几个字节,存储下一个位置的指针

		//32位和64位指针的大小是不一样的,所以_FreeList 采用void*的形式
		//*((void**)obj) = _FreeList;//void**解引用出来 -> void*在32位就是4字节大小,64位就是8字节大小
		NextObj(obj) = _FreeList;//将上面的封装起来
		_FreeList = obj;//第一个点
	}

	void Destory()//释放所有内存
	{
		while (BlockHead->_next)
		{
			BlockNode *node = BlockHead->_next;
			BlockHead->_next = node->_next;
			_memory = nullptr;
			_FreeList = nullptr;
			_LeftSize = 0;
			delete node;
		}
	}

	~ObjectPool()
	{
		Destory();
	}

private:
	char *_memory = nullptr;//申请的大块内存
	void *_FreeList = nullptr;//自由链表
	int _LeftSize = 0;//申请的内存剩余大小,防止越界
	int _count = 1;//记录申请大块内存的次数
	size_t _ObjSize=GetObjSize();
	BlockNode *BlockHead = new BlockNode;
};

--------测试代码-------------
#pragma once
#include "common.h"
#include "ObjectPoll.h"

typedef struct Node
{
	struct Node *left;
	struct Node *right;
	int val;
}node;

int main()
{

	size_t t1 = clock();
	for (int i = 0; i < 10000000; i++)
	{
		node *p = (node*)malloc(sizeof(node));
	}
	size_t t2 = clock();
	cout <<"直接调用malloc使用的时间" <<t2 - t1 << endl;

	size_t t3 = clock();
	ObjectPool<node> pool;
	for (int i = 0; i < 10000000; i++)
	{
		node *p = pool.New();
	}
	
	size_t t4 = clock();
	cout <<"使用内存池的时间:" <<t4 - t3 << endl;

	getchar();
	return 0;
}

image-20210702110523306

以上是关于内存池介绍的主要内容,如果未能解决你的问题,请参考以下文章

newCacheThreadPool()newFixedThreadPool()newScheduledThreadPool()newSingleThreadExecutor()自定义线程池(代码片段

内存池介绍

深入JVM - Code Cache内存池

深入JVM - Code Cache内存池

简单实现定长内存池

高并发内存池的介绍