欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C++如何實(shí)現(xiàn)定長(zhǎng)內(nèi)存池詳解

 更新時(shí)間:2021年09月20日 10:00:33   作者:沒(méi)什么..  
內(nèi)存池根據(jù)存儲(chǔ)的元素的長(zhǎng)度是否可變,分為變長(zhǎng),與定長(zhǎng)兩種內(nèi)存池,這篇文章主要給大家介紹了關(guān)于C++如何實(shí)現(xiàn)定長(zhǎng)內(nèi)存池的相關(guān)資料,需要的朋友可以參考下

1. 池化技術(shù)

池是在計(jì)算機(jī)技術(shù)中經(jīng)常使用的一種設(shè)計(jì)模式,其內(nèi)涵在于:將程序中需要經(jīng)常使用的核心資源先申請(qǐng)出來(lái),放到一個(gè)池內(nèi),由程序自己管理,這樣可以提高資源的使用效率,也可以保證本程序占有的資源數(shù)量。 經(jīng)常使用的池技術(shù)包括內(nèi)存池、線程池和連接池(數(shù)據(jù)庫(kù)經(jīng)常使用到)等,其中尤以內(nèi)存池和線程池使用最多。

2. 內(nèi)存池概念

內(nèi)存池(Memory Pool) 是一種動(dòng)態(tài)內(nèi)存分配與管理技術(shù)。 通常情況下,程序員習(xí)慣直接使用 new、delete、malloc、free 等API申請(qǐng)分配和釋放內(nèi)存,這樣導(dǎo)致的后果是:當(dāng)程序長(zhǎng)時(shí)間運(yùn)行時(shí),由于所申請(qǐng)內(nèi)存塊的大小不定,頻繁使用時(shí)會(huì)造成大量的內(nèi)存碎片從而降低程序和操作系統(tǒng)的性能。內(nèi)存池則是在真正使用內(nèi)存之前,先申請(qǐng)分配一大塊內(nèi)存(內(nèi)存池)留作備用,當(dāng)程序員申請(qǐng)內(nèi)存時(shí),從池中取出一塊動(dòng)態(tài)分配,當(dāng)程序員釋放內(nèi)存時(shí),將釋放的內(nèi)存再放入池內(nèi),再次申請(qǐng)池可以 再取出來(lái)使用,并盡量與周邊的空閑內(nèi)存塊合并。若內(nèi)存池不夠時(shí),則自動(dòng)擴(kuò)大內(nèi)存池,從操作系統(tǒng)中申請(qǐng)更大的內(nèi)存池。

2.1 內(nèi)存碎片

  • 內(nèi)碎片:

內(nèi)部碎片就是已經(jīng)被分配出去(能明確指出屬于哪個(gè)進(jìn)程)卻不能被利用的內(nèi)存空間;內(nèi)部碎片是處于區(qū)域內(nèi)部或頁(yè)面內(nèi)部的存儲(chǔ)塊。占有這些區(qū)域或頁(yè)面的進(jìn)程并不使用這個(gè) 存儲(chǔ)塊。而在進(jìn)程占有這塊存儲(chǔ)塊時(shí),系統(tǒng)無(wú)法利用它。直到進(jìn)程釋放它,或進(jìn)程結(jié)束時(shí),系統(tǒng)才有可能利用這個(gè)存儲(chǔ)塊。(編譯器會(huì)對(duì)數(shù)據(jù)進(jìn)行對(duì)齊操作,當(dāng)不是編譯器的最小對(duì)齊數(shù)的整數(shù)倍的時(shí)候需要添加一些來(lái)保證對(duì)齊,那么這塊為了對(duì)齊而添加的就是內(nèi)碎片)

  • 外碎片(通常所講的內(nèi)存碎片):

假設(shè)系統(tǒng)依次分配了16byte、8byte、16byte、4byte,還剩余8byte未分配。這時(shí)要分配一個(gè)24byte的空間,操作系統(tǒng)回收了一個(gè)上面的兩個(gè)16byte,總的剩余空間有40byte,但是卻不能分配出一個(gè)連續(xù)24byte的空間,這就是外碎片問(wèn)題。(本來(lái)有足夠的內(nèi)存,但是由于碎片化無(wú)法申請(qǐng)到稍大一些的連續(xù)內(nèi)存)

3. 實(shí)現(xiàn)定長(zhǎng)內(nèi)存池

3.1 定位new表達(dá)式(placement-new)

定位new表達(dá)式是在已分配的原始內(nèi)存空間中調(diào)用構(gòu)造函數(shù)初始化一個(gè)對(duì)象。使用格式:new (place_address) type或者new (place_address) type(initializer-list),place_address必須是一個(gè)指針,initializer-list是類型的初始化列表。
使用場(chǎng)景:

定位new表達(dá)式在實(shí)際中一般是配合內(nèi)存池使用。因?yàn)閮?nèi)存池分配出的內(nèi)存沒(méi)有初始化,所以如果是自定義類型的對(duì)象,需要**使用new的定義表達(dá)式進(jìn)行顯示調(diào)構(gòu)造函數(shù)**進(jìn)行初始化。

3.2 完整實(shí)現(xiàn)

即實(shí)現(xiàn)一個(gè) FreeList,每個(gè) FreeList 用于分配固定大小的內(nèi)存塊,比如用于分配 32字節(jié)對(duì)象的固定內(nèi)存分配器,之類的。

優(yōu)點(diǎn):
簡(jiǎn)單粗暴,分配和釋放的效率高,解決實(shí)際中特定場(chǎng)景下的問(wèn)題有效。

缺點(diǎn):
功能單一,只能解決定長(zhǎng)的內(nèi)存需求,另外占著內(nèi)存沒(méi)有釋放。

實(shí)現(xiàn)的思想:

  1. 先向內(nèi)存申請(qǐng)一塊大的內(nèi)存,如果需要,那么就對(duì)這塊已經(jīng)申請(qǐng)出來(lái)的內(nèi)存進(jìn)行切割(減少了和操作系統(tǒng)底層打交道的次數(shù),效率也就提高了,內(nèi)存池一定是可以解決申請(qǐng)和釋放內(nèi)存的效率的)
  2. 對(duì)于不需要的小塊內(nèi)存,并不是將其進(jìn)行釋放掉,而是使用一個(gè)freeList將他們管理起來(lái),如果freeList中有了空余的,那么再次申請(qǐng)內(nèi)存首先會(huì)到自由鏈表中取,而不是去申請(qǐng)出來(lái)的大內(nèi)存塊進(jìn)行切割
  3. 對(duì)于這個(gè)申請(qǐng)出來(lái)的小塊內(nèi)存,前4個(gè)或者8個(gè)字節(jié)存放的是下一個(gè)小內(nèi)存塊的地址(這是由于在32位平臺(tái)下指針的大小是4字節(jié),在64位平臺(tái)下指針則是8字節(jié)),這里如何巧妙的進(jìn)行平臺(tái)下指針大小的適配,需要好好的進(jìn)行琢磨。
  4. (幫助理解3)指針就是地址,那么指針的類型是為了解引用取到大小,對(duì)于所申請(qǐng)出來(lái)的內(nèi)存的類型我是不關(guān)心的,在32位平臺(tái)下我就想取出他的前4個(gè)字節(jié),然后存放我的下一個(gè)小內(nèi)存的地址,所以把obj強(qiáng)轉(zhuǎn)為int*類型,然后在解引用就可以拿到前4個(gè)字節(jié)。那如果在64位平臺(tái)下,就應(yīng)該取其前8個(gè)字節(jié)來(lái)存放下一個(gè)小內(nèi)存的地址,但是如果都寫為取前4個(gè)字節(jié)的話,這里就會(huì)發(fā)生指針越界的問(wèn)題。下述代碼所寫的Nextobj()接口函數(shù)就是為了能夠取出小內(nèi)存中的前4個(gè)字節(jié)或者8個(gè)字節(jié)。我需要的類型是void*,可以自動(dòng)的適配平臺(tái)(類比于上述的int類型,就可以相通)
//實(shí)現(xiàn)一個(gè)定長(zhǎng)的內(nèi)存池(針對(duì)某一個(gè)具體的對(duì)象,所以起名字叫ObjictPool)
#pragma once 

#include"Common.h"

template<class T>
class ObjectPool
{
public:
	~ObjectPool()
	{
		//
	}
	//此時(shí)代碼還存一個(gè)很大的問(wèn)題:我們默認(rèn)這里取的是前四個(gè)字節(jié),但是在64位的平臺(tái)下,需要取的應(yīng)該是這塊小內(nèi)存的前8個(gè)字節(jié)來(lái)保存地址
	void*& Nextobj(void* obj)
	{
		return *((void**)obj); //對(duì)于返回的void*可以自動(dòng)的適配平臺(tái)
	}
	//申請(qǐng)內(nèi)存的函數(shù)接口
	T* New()
	{
		T* obj = nullptr;
		//一上來(lái)首先應(yīng)該判斷freeList
		if (_freeList)
		{
			//那就直接從自由鏈表中取一塊出來(lái)
			obj = (T*)_freeList;
			//_freeList = (void*)(*(int*)_freeList);
			_freeList = Nextobj(_freeList);
		}
		else
		{
			//表示自由鏈表是空的
			//那么這里又要進(jìn)行判斷,memory有沒(méi)有
			if (_leftSize < sizeof(T)) //說(shuō)明此時(shí)空間不夠了
			{
				//那么就進(jìn)行切割
				_leftSize = 1024 * 100;
				_memory = (char*)malloc(_leftSize);
				//對(duì)于C++來(lái)說(shuō),如果向系統(tǒng)申請(qǐng)失敗了,則會(huì)拋異常
				if (_memory == nullptr)
				{
					throw std::bad_alloc();
				}
			}
			//進(jìn)行memory的切割
			obj = (T*)_memory;
			_memory += sizeof(T); //這里如果想不通可以畫一下圖,很簡(jiǎn)單
			_leftSize -= sizeof(T); //表示剩余空間的大小
		}
		new(obj)T;  //定位new,因?yàn)閯偵暾?qǐng)的空間內(nèi)如果是自定義類型是沒(méi)有初始化的
		//所以需要可以顯示的調(diào)用這個(gè)類型的構(gòu)造函數(shù),這個(gè)是專門配合內(nèi)存池使用的
		return obj;
	}

	void Delete(T* obj)
	{
		obj->~T();//先把自定義類型進(jìn)行析構(gòu)
		//然后在進(jìn)行釋放,但是此時(shí)還回來(lái)的都是一塊一塊的小內(nèi)存,無(wú)法做到一次性進(jìn)行free,所以需要一個(gè)自由鏈表將這些小內(nèi)存都掛接住
		//這里其實(shí)才是核心的關(guān)鍵點(diǎn)
		//對(duì)于指針來(lái)說(shuō),在32位的平臺(tái)下面是4字節(jié),在64位平臺(tái)下面是8字節(jié)

		//頭插到freeList
		//*((int*)obj)= (int)_freeList;
		Nextobj(obj) = _freeList;
		_freeList = obj;
	}
private:
	char* _memory = nullptr;//這里給char*是為了好走大小,并不是一定要給T*或者void*
	int _leftSize = 0; //為什么會(huì)加入這個(gè)成員變量呢?因?yàn)槟愕膍enory += sizeof(T),有可能就會(huì)造成越界的問(wèn)題
	void* _freeList = nullptr; //給一些缺省值,讓他的構(gòu)造函數(shù)自己生成就可以了
};

struct TreeNode
{
	int _val;
	TreeNode* _left;
	TreeNode* _right;

	TreeNode()
		:_val(0)
		, _left(nullptr)
		, _right(nullptr)
	{}
};
void TestObjectPool()
{

	驗(yàn)證還回來(lái)的內(nèi)存是否重復(fù)利用的問(wèn)題
	ObjectPool<TreeNode> tnPool;
	TreeNode* node1 = tnPool.New();
	TreeNode* node2 = tnPool.New();
	cout << node1 << endl;
	cout << node2 << endl;

	tnPool.Delete(node1);
	TreeNode* node3 = tnPool.New();
	cout << node3 << endl;

	cout << endl;

	//驗(yàn)證內(nèi)存池到底快不快,有沒(méi)有做到性能的優(yōu)化
	//new底層本身調(diào)用的malloc,會(huì)一直和操作系統(tǒng)的底部打交道
	size_t begin1 = clock();
	std::vector<TreeNode*> v1;
	for (int i = 0; i < 1000000; ++i)
	{
		v1.push_back(new TreeNode);
	}
	for (int i = 0; i < 1000000; ++i)
	{
		delete v1[i];
	}
	size_t end1 = clock();


	//這里我們調(diào)用自己所寫的內(nèi)存池
	ObjectPool<TreeNode> tnPool;
	size_t begin2 = clock();
	std::vector<TreeNode*> v2;
	for (int i = 0; i < 1000000; ++i)
	{
		v2.push_back(tnPool.New());
	}
	for (int i = 0; i < 1000000; ++i)
	{
		tnPool.Delete(v2[i]);
	}
	size_t end2 = clock();

	cout << end1 - begin1 << endl;
	cout << end2 - begin2 << endl;
}

這個(gè)定長(zhǎng)的內(nèi)存池依舊存在著大量的問(wèn)題:

  1. 我們所采用的是取這塊小內(nèi)存的前4個(gè)或者8個(gè)字節(jié)來(lái)存放下一個(gè)小內(nèi)存的地址,但是如果這里的模板類型T是一個(gè)char類型怎么辦,它本身都沒(méi)有4字節(jié),怎么來(lái)存放?(解決的辦法就是,進(jìn)行一次判斷如果sizeof(T) < sizeof(T*)的大小,那么就開辟T*的大?。?/li>
  2. 無(wú)法編寫這個(gè)ObjectPool的析構(gòu)函數(shù),因?yàn)樯暾?qǐng)的都是一個(gè)個(gè)的小塊內(nèi)存,但是對(duì)于free來(lái)說(shuō),應(yīng)該是一次性的對(duì)整個(gè)所開辟出來(lái)的內(nèi)存塊都進(jìn)行釋放(解決的辦法就是,將這些向操作系統(tǒng)申請(qǐng)的大塊內(nèi)存也管理起來(lái),如果小塊內(nèi)存都還回來(lái)了,那么就可以對(duì)這個(gè)大塊內(nèi)存進(jìn)行釋放)

對(duì)于上述的具體實(shí)現(xiàn)可以參考下面這篇文章寫的很詳細(xì):

如何設(shè)計(jì)一個(gè)簡(jiǎn)單內(nèi)存池

總結(jié)

到此這篇關(guān)于C++如何實(shí)現(xiàn)定長(zhǎng)內(nèi)存池的文章就介紹到這了,更多相關(guān)C++定長(zhǎng)內(nèi)存池內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • C++深度探索運(yùn)算符重載和返回值優(yōu)化

    C++深度探索運(yùn)算符重載和返回值優(yōu)化

    這篇文章主要介紹了C++運(yùn)算符重載及編譯器返回值優(yōu)化,C++當(dāng)中除了函數(shù)可以重載之外,其實(shí)運(yùn)算符也是可以重載的,下面一起來(lái)詳細(xì)了解吧
    2022-04-04
  • C++實(shí)現(xiàn)順序排序算法簡(jiǎn)單示例代碼

    C++實(shí)現(xiàn)順序排序算法簡(jiǎn)單示例代碼

    這篇文章主要介紹了C++實(shí)現(xiàn)順序排序算法簡(jiǎn)單示例代碼,對(duì)于學(xué)過(guò)C++的朋友一定不會(huì)陌生,現(xiàn)在重溫一下這個(gè)算法,需要的朋友可以參考下
    2014-08-08
  • C語(yǔ)言近萬(wàn)字為你講透樹與二叉樹

    C語(yǔ)言近萬(wàn)字為你講透樹與二叉樹

    樹是計(jì)算機(jī)算法最重要的非線性結(jié)構(gòu)。因?yàn)闃淠芎芎玫孛枋鼋Y(jié)構(gòu)的分支關(guān)系和層次特性,所以在計(jì)算機(jī)科學(xué)和計(jì)算機(jī)應(yīng)用領(lǐng)域有著廣泛的應(yīng)用。這篇文章我就帶大家一起了解一下樹、二叉樹這種結(jié)構(gòu),下篇文章會(huì)重點(diǎn)向大家介紹二叉樹的遍歷算法
    2022-05-05
  • C語(yǔ)言實(shí)現(xiàn)客房管理系統(tǒng)

    C語(yǔ)言實(shí)現(xiàn)客房管理系統(tǒng)

    這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)客房管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-08-08
  • C語(yǔ)言中static與sizeof查缺補(bǔ)漏篇

    C語(yǔ)言中static與sizeof查缺補(bǔ)漏篇

    static在修飾變量的時(shí)候,如果是修飾全局變量,則跟全局變量功能一樣;如果是修改局部變量,則每次調(diào)用的時(shí)候,保持著上一次的值;而sizeof是用來(lái)判斷一個(gè)變量及數(shù)據(jù)類型所占字節(jié)數(shù)的,下面我們?cè)敿?xì)來(lái)看看
    2022-07-07
  • C語(yǔ)言中關(guān)于scanf讀取緩存區(qū)的問(wèn)題

    C語(yǔ)言中關(guān)于scanf讀取緩存區(qū)的問(wèn)題

    scanf()函數(shù)是通用終端格式化輸入函數(shù),它從標(biāo)準(zhǔn)輸入設(shè)備(鍵盤) 讀取輸入的信息,接下來(lái)通過(guò)本文給大家介紹C語(yǔ)言中關(guān)于scanf讀取緩存區(qū)的問(wèn)題,需要的朋友一起看看吧
    2021-09-09
  • C++ LeeCode題目:比特位計(jì)數(shù)和買賣股票的最佳時(shí)機(jī)

    C++ LeeCode題目:比特位計(jì)數(shù)和買賣股票的最佳時(shí)機(jī)

    這篇文章主要介紹了基于C語(yǔ)言計(jì)算比特位計(jì)數(shù)和買賣股票的最佳時(shí)機(jī),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2021-07-07
  • C語(yǔ)言三子棋小游戲的實(shí)現(xiàn)代碼

    C語(yǔ)言三子棋小游戲的實(shí)現(xiàn)代碼

    這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言三子棋小游戲的實(shí)現(xiàn)代碼,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-11-11
  • 詳解C++編程中用數(shù)組名作函數(shù)參數(shù)的方法

    詳解C++編程中用數(shù)組名作函數(shù)參數(shù)的方法

    這篇文章主要介紹了詳解C++編程中用數(shù)組名作函數(shù)參數(shù)的方法,是C++入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下
    2015-09-09
  • C語(yǔ)言實(shí)現(xiàn)學(xué)生信息管理系統(tǒng)

    C語(yǔ)言實(shí)現(xiàn)學(xué)生信息管理系統(tǒng)

    這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)學(xué)生信息管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-07-07

最新評(píng)論