溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

C++內(nèi)存池的實(shí)現(xiàn)方法

發(fā)布時間:2021-07-13 14:54:17 來源:億速云 閱讀:245 作者:chen 欄目:開發(fā)技術(shù)

這篇文章主要講解了“C++內(nèi)存池的實(shí)現(xiàn)方法”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“C++內(nèi)存池的實(shí)現(xiàn)方法”吧!

目錄
  • 一、內(nèi)存池基礎(chǔ)知識

    • 1、什么是內(nèi)存池

      • 1.1 池化技術(shù)

      • 1.2 內(nèi)存池

    • 2、內(nèi)存池的作用

      • 2.1 效率問題

      • 2.2 內(nèi)存碎片

    • 3、內(nèi)存池技術(shù)的演進(jìn)

    • 二、簡易內(nèi)存池原理

      • 1、整體設(shè)計

        • 1.1 內(nèi)存池結(jié)構(gòu)

        • 1.2 申請內(nèi)存

        • 1.3 釋放內(nèi)存

      • 2、詳細(xì)剖析

        • 2.1 blockNode結(jié)構(gòu)

        • 2.2 單個對象的大小

      • 3、性能比較

      • 三、簡易內(nèi)存池完整源碼

        一、內(nèi)存池基礎(chǔ)知識

        1、什么是內(nèi)存池

        1.1 池化技術(shù)

        池化技術(shù)是計算機(jī)中的一種設(shè)計模式,主要是指:將程序中經(jīng)常要使用的計算機(jī)資源預(yù)先申請出來,由程序自己管理,程序在使用時直接從“池”中獲取,不僅保證了程序占有的資源數(shù)量同時減少資源的申請和釋放時間。常見的池化技術(shù)有內(nèi)存池、線程池、連接池等。

        1.2 內(nèi)存池

        內(nèi)存池是一種動態(tài)內(nèi)存分配與管理技術(shù)。它的核心思想是:預(yù)先申請一段內(nèi)存空間,使用一種高效的數(shù)據(jù)結(jié)構(gòu)(哈希、鏈表)進(jìn)行管理,當(dāng)程序需要內(nèi)存時直接從內(nèi)存池中分配一塊內(nèi)存給程序,同樣當(dāng)使用完時在歸還給內(nèi)存池。這樣做的好處是,減少直接使用new/delete、malloc/free等API申請和釋放內(nèi)存的時間,提高程序運(yùn)行效率;同時,程序每次直接使用new/delete、malloc/free從內(nèi)存中申請空間,會導(dǎo)致內(nèi)存碎片問題,內(nèi)存池直接申請大塊內(nèi)存就減少了內(nèi)存碎片。

        2、內(nèi)存池的作用

        2.1 效率問題

        通常申請內(nèi)存都是通過new/delete、malloc/free接口直接從內(nèi)存的堆區(qū)申請一塊內(nèi)存,釋放也是直接釋放到堆中。頻繁的申請和釋放必然消耗大量時間,降低程序的運(yùn)行效率。

        例如:假設(shè)每個鏈表的節(jié)點(diǎn)大小為16字節(jié),當(dāng)鏈表需要經(jīng)常插入節(jié)點(diǎn)時,必然就需要頻繁的內(nèi)存申請操縱,每次從堆中申請16個字節(jié)都要一定的時間開銷,釋放內(nèi)存也需要時間開銷。使用內(nèi)存池,我們可以直接從內(nèi)存中申請“一批節(jié)點(diǎn)”,當(dāng)程序需要內(nèi)存時不用直接去堆中申請,直接將預(yù)先申請好的內(nèi)存分配給程序。

        2.2 內(nèi)存碎片

        頻繁的從內(nèi)存中申請小塊內(nèi)存會導(dǎo)致內(nèi)存碎片問題。內(nèi)存碎片分為內(nèi)碎片和外碎片兩種。

        1)外碎片

        外碎片也就是我們常說的內(nèi)存碎片。例如:我們每次從內(nèi)存中申請一塊16字節(jié)大小的內(nèi)存,內(nèi)存中就會存在很多16個字節(jié)大小的塊,當(dāng)該內(nèi)存釋放時就可能造成內(nèi)存碎片,如下圖:

        內(nèi)存中空閑內(nèi)存大小為88字節(jié),但是我們能申請的最大內(nèi)存塊為21字節(jié)。

        C++內(nèi)存池的實(shí)現(xiàn)方法

        2)內(nèi)碎片

        內(nèi)碎片是指已經(jīng)分配出去的內(nèi)存中存在的未使用的小塊內(nèi)存。內(nèi)存池技術(shù)雖然解決了內(nèi)存隨便但是又造成了內(nèi)碎片問題,內(nèi)碎片不可避免但是可以通過程序的優(yōu)化減少內(nèi)存內(nèi)碎片。

        例如:實(shí)際需要是申請10byte的內(nèi)存,定長內(nèi)存池可能會進(jìn)行內(nèi)存對齊,一次性分配了16個字節(jié)的內(nèi)存,多余的6字節(jié)實(shí)際并未使用,這6字節(jié)就是內(nèi)存內(nèi)碎片。

        3、內(nèi)存池技術(shù)的演進(jìn)

        1)最最最最“簡單”的內(nèi)存池

        做一個鏈表,指向空閑的內(nèi)存。分配就是從鏈表中取出來一塊返回pop,釋放就是將內(nèi)存在push到鏈表中。需要做好歸并,標(biāo)記和保護(hù),防止內(nèi)存二次釋放問題。

        2)定長內(nèi)存池

        實(shí)現(xiàn)一個FreeList類,它的本質(zhì)是一個鏈表,節(jié)點(diǎn)是一塊固定大小的內(nèi)存,采用頭插和頭刪的方式申請釋放內(nèi)存。每個固定內(nèi)存分配器里面有兩個鏈表:OpenList用于存儲未分配的空閑內(nèi)存對象(FreeList對象),CloseList用于存儲已經(jīng)分配的內(nèi)存對象。

        分配內(nèi)存就是從IOpenLsit中取出一個對象給程序,釋放內(nèi)存就是將對象push到CloseList里。當(dāng)內(nèi)存不夠時,OpenList申請一個大塊內(nèi)存在切割成固定的長度大小的小塊內(nèi)存。

        C++內(nèi)存池的實(shí)現(xiàn)方法

        3)C++STL庫中的內(nèi)存池

        定長內(nèi)存池存在的問題就是只能申請固定長度的內(nèi)存,而實(shí)際中我們需要申請的內(nèi)存大小可能是不管固定,在C++STL庫中,采用哈希表和定長內(nèi)存池結(jié)合的方式實(shí)現(xiàn)了一個內(nèi)存池。具體如下

        構(gòu)造多個定長內(nèi)存池,以一個固定的對齊數(shù)進(jìn)行對齊(例如以8字節(jié)進(jìn)行對齊),第一個定長內(nèi)存池的內(nèi)存對象大小為8(至少得能保證無論在64位還是32位系統(tǒng)下都可以保存下一個指針類型),第二個內(nèi)存池對象大小為16...最后一個內(nèi)存池對象大小為128byte,當(dāng)申請的內(nèi)存大小超過128字節(jié)時,通過二級空間配置器申請(直接從內(nèi)存中申請)。

        構(gòu)造一個哈希表,將不同大小的內(nèi)存對象掛在哈希表中。如下圖:

        C++內(nèi)存池的實(shí)現(xiàn)方法

        申請內(nèi)存:加入要申請的內(nèi)存大小為8字節(jié)直接在index  = 0處分配一塊內(nèi)存,當(dāng)然申請的內(nèi)存小于8字節(jié)時也會直接分配8字節(jié)的內(nèi)存。當(dāng)Free_list[index]為nullptr時從內(nèi)存中申請一塊內(nèi)存,切割成固定大小‘掛在'Free_list[index]位置。

        釋放內(nèi)存:根據(jù)內(nèi)存對象大小,計算index在插入到哈希表中的index位置。

        二、簡易內(nèi)存池原理

        1、整體設(shè)計

        1.1 內(nèi)存池結(jié)構(gòu)

        兩個鏈表,RequestMemory和ReleaseMemory。

        RequestMemory鏈表存儲的是使用new或者malloc從物理內(nèi)存申請的還沒有被使用的內(nèi)存塊,是一個個的memNode節(jié)點(diǎn)。

        ReleaseMemory鏈表存儲的是使用完釋放回來的固定大小的內(nèi)存塊。

        C++內(nèi)存池的實(shí)現(xiàn)方法

        1.2 申請內(nèi)存
        •  先在ReleaseMemory找,如果有內(nèi)存則直接pop使用

        • ReleaseMemory為nullptr時,在RequestMemory中找。

        • RequestMemory的頭節(jié)點(diǎn)表示的是新申請的,申請內(nèi)存時只需要在頭結(jié)點(diǎn)中找,判斷頭結(jié)點(diǎn)的useCount和sumCount是否相等。當(dāng)useCount等于sumCount時表示已經(jīng)用完了,就需要去物理內(nèi)存中申請,否則直接從表頭push一塊。

        • 去物理內(nèi)存申請內(nèi)存時,申請的大小是上一次申請內(nèi)存塊大小的二倍,并將申請的內(nèi)存塊push到RequestMemory頭部。

        1.3 釋放內(nèi)存

        釋放內(nèi)存時,直接將要釋放的內(nèi)存push到ReleaseMemory的頭部即可。

        2、詳細(xì)剖析

        2.1 blockNode結(jié)構(gòu)

        blockNode表示一個個新申請的內(nèi)存塊,用一個結(jié)構(gòu)體進(jìn)行管理。blockNode成員如下:

        • void* _memory:表示新申請的內(nèi)存塊的首地址

        • BlockNode * _next:存儲next節(jié)點(diǎn)

        • _objNum:內(nèi)存塊對象的個數(shù)

        注意:blockNode的大小每次都是上一次的二倍,是一個質(zhì)數(shù)增長,因此應(yīng)該設(shè)置一個上限,當(dāng)?shù)竭_(dá)一定大小后進(jìn)行線性增長。這里規(guī)定,最大內(nèi)存塊的大小為100000*sizeof(T),T表示的是申請的節(jié)點(diǎn)類型。

        2.2 單個對象的大小

        這里的單個對象指的ReleaseMemory的節(jié)點(diǎn)大小,當(dāng)用戶申請的內(nèi)存大小sizeof(T)小于sizeof(T*)時,為了能夠?qū)⒃搶ο箧溄拥絉eleaseMemory中,應(yīng)該按照T*進(jìn)行分配。

        3、性能比較

        分別使用malloc/free、new/delete、memPool申請和釋放110000個內(nèi)存,時間如下:

        C++內(nèi)存池的實(shí)現(xiàn)方法

        三、簡易內(nèi)存池完整源碼

        #include<iostream>
        #include<vector>
        #include<ctime>
        using namespace std;
         
        template<class T>
        class MemPool
        {
        private:
        	//內(nèi)存塊結(jié)構(gòu)
        	typedef struct BlockNode
        	{
        		void* _memory;//內(nèi)存塊地址
        		BlockNode* _next;//下一個blockNode
        		size_t _objNum;//內(nèi)存塊對象的個數(shù)
        		//構(gòu)造函數(shù)---num表示申請對象的個數(shù)
        		BlockNode(size_t num)
        			:_objNum(num),
        			_next(nullptr)
        		{
        			_memory = malloc(_objNum*_size);
        		}
         
        		~BlockNode()
        		{
        			free(_memory);
        			_memory = nullptr;
        			_next = nullptr;
        			_objNum = 0;
        		}
        	}BlockNode;
        protected:
        	static size_t _size;//單個對象的大小
        	T* _releaseMemory = nullptr;//釋放的內(nèi)存
        	BlockNode* _requestMemory;//申請的內(nèi)存塊
        	size_t _maxNum;//內(nèi)存塊最大的大小
        	size_t _useCount;//當(dāng)前內(nèi)存塊已經(jīng)使用的對象個數(shù)
        protected:
        	//設(shè)置單個對象的大小
        	static size_t setSize()
        	{
        		return (sizeof(T) >= sizeof(T*) ? sizeof(T):sizeof(T*));
        	}
        public:
        	MemPool()
        		:_useCount(0),
        		_releaseMemory(nullptr),
        		_maxNum(100000*_size)
        	{
        		//開始先申請32個_size大小的空間
        		_requestMemory = new BlockNode(32);
        	}
         
        	~MemPool()
        	{
        		BlockNode *cur = _requestMemory;
        		while (cur)
        		{
        			BlockNode* del = cur;
        			cur = cur->_next;
        			delete del;            //會自動調(diào)用~BlockNode()
        		}
        	}
         
        	T* New()
        	{
        		//先在releaseMemory中找
        		if (_releaseMemory)
        		{
        			T* obj = _releaseMemory;
        			_releaseMemory = *((T**)_releaseMemory);//releaseMemory的前幾個字節(jié)存儲的是下一個節(jié)點(diǎn)的地址
        			return obj;
        		}
        		else
        		{
        			//判斷requesetMemory中是否還有空閑內(nèi)存
        			if (_requestMemory->_objNum == _useCount)
        			{
        				//取物理內(nèi)存中申請一塊內(nèi)存
        				size_t size = 2 * _useCount >= _maxNum ? _maxNum : 2 * _useCount;
        				BlockNode* newBlock = new BlockNode(size);
         
        				newBlock->_next = _requestMemory;
        				_requestMemory = newBlock;
        				_useCount = 0;
        			}
        			//走到這里,一定有內(nèi)存
        			T* obj = (T*)((char*)_requestMemory->_memory+_useCount*_size);
         
        			_useCount++;
        			return new(obj)T();//用定位new對這塊空間初始化
        		}
        	}
         
        	void Delete(T* obj)
        	{
        		if (obj)
        		{
        			obj->~T();
         
        			*((T**)obj) = _releaseMemory;
        			_releaseMemory = obj;
        		}
        	}
        };
         
        //靜態(tài)成員變量,類外初始化
        template<typename T>
        size_t MemPool<T>::_size = MemPool<T>::setSize();
         
        struct TreeNode
        {
        	int _val;
        	TreeNode* _left;
        	TreeNode* _right;
        };
        void test1()
        {
        	MemPool<TreeNode> mp;
         
        	vector<TreeNode*> v;
        	for (int i = 0; i < 10; i++)
        	{
        		TreeNode* mem = mp.New();
        		v.push_back(mem);
        	}
         
        	for (int i = 0; i < 10; i++)
        	{
        		mp.Delete(v[i]);
        	}
        }

        感謝各位的閱讀,以上就是“C++內(nèi)存池的實(shí)現(xiàn)方法”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對C++內(nèi)存池的實(shí)現(xiàn)方法這一問題有了更深刻的體會,具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識點(diǎn)的文章,歡迎關(guān)注!

        向AI問一下細(xì)節(jié)

        免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

        c++
        AI