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

C++內(nèi)存池兩種方案解析

 更新時(shí)間:2021年08月30日 09:54:34   作者:Hickey Zhang  
這篇文章主要詳情介紹了C++內(nèi)存池兩種方案做對(duì)比,對(duì)此感興趣的小伙伴一起來(lái)看看吧

C++內(nèi)存池

前言:

使用new expression為類(lèi)的多個(gè)實(shí)例分配動(dòng)態(tài)內(nèi)存時(shí),cookie導(dǎo)致內(nèi)存利用率可能不高,此時(shí)我們通過(guò)實(shí)現(xiàn)類(lèi)的內(nèi)存池來(lái)降低overhead。從不成熟到巧妙優(yōu)化的內(nèi)存池,得益于union的分時(shí)復(fù)用特性,內(nèi)存利用率得到了提高。

1、C++內(nèi)存池分析

在實(shí)例化某個(gè)類(lèi)的對(duì)象時(shí)(在heap而不是stack中),若不使用array new,則每次實(shí)例化時(shí)都要調(diào)用一次內(nèi)存分配函數(shù),類(lèi)的每個(gè)實(shí)例在內(nèi)存中都有上下兩個(gè)cookie,從而降低了內(nèi)存的利用率。然而,array new也有先天的缺陷,即只能調(diào)用默認(rèn)無(wú)參構(gòu)造函數(shù),這對(duì)于很多沒(méi)有提供無(wú)參構(gòu)造函數(shù)的類(lèi)來(lái)說(shuō)是不合適的。

因此,我們可以對(duì)于一個(gè)沒(méi)有實(shí)例化的類(lèi)第一次實(shí)例化時(shí),先分配一大塊內(nèi)存(內(nèi)存池),這一大塊內(nèi)存記錄在類(lèi)中,只有上下兩個(gè)cookie,能夠容納多個(gè)實(shí)例。后續(xù)實(shí)例化時(shí),若內(nèi)存池中還有剩余內(nèi)存,則不必申請(qǐng)內(nèi)存分配,只在內(nèi)存池中分配。內(nèi)存回收時(shí),將實(shí)例所占用的內(nèi)存回收到內(nèi)存池中。若內(nèi)存池中無(wú)內(nèi)存,則再申請(qǐng)分配大塊內(nèi)存。

2、多此一舉方案

我們以鏈表的形式組織內(nèi)存池,內(nèi)存池中鏈表的每個(gè)結(jié)點(diǎn)是一個(gè)小桶,這個(gè)桶中裝我們實(shí)例化的對(duì)象。

內(nèi)存池鏈表的頭結(jié)點(diǎn)記錄在類(lèi)中,即以class staic變量的形式存儲(chǔ)。組織形式如下:

實(shí)現(xiàn)代碼如下:

#include <iostream>
using namespace std;
class DemoClass{
public:
    DemoClass() = default;
    DemoClass(int i):data(i){}
    static void* operator new(size_t size);
    static void operator delete(void *);
    virtual ~DemoClass(){}
private:
    DemoClass *next;
    int data;
    static DemoClass *freeMemHeader;
    static const size_t POOL_SIZE;
};
DemoClass * DemoClass::freeMemHeader = nullptr;
const size_t DemoClass::POOL_SIZE = 24;//設(shè)定內(nèi)存池能容納24個(gè)DemoClass對(duì)象
void* DemoClass::operator new(size_t size){
    DemoClass* p;
    if(!freeMemHeader){//freeMemHeader為空,內(nèi)存池中無(wú)空間,分配內(nèi)存
        size_t pool_mem_bytes = size * POOL_SIZE;//內(nèi)存池的字節(jié)大小 = 每個(gè)實(shí)例的大?。ㄗ止?jié)數(shù))* 內(nèi)存池中能容納的最大實(shí)例數(shù)
        freeMemHeader = reinterpret_cast<DemoClass*>(new char[pool_mem_bytes]);//new char[]分配pool_mem_bytes個(gè)字節(jié),因?yàn)槊總€(gè)char占用1個(gè)字節(jié)
        cout << "Info:向操作系統(tǒng)申請(qǐng)了" << pool_mem_bytes << "字節(jié)的內(nèi)存。" << endl;
        for(int i = 0;i < POOL_SIZE - 1; ++i){//將內(nèi)存池中POOL_SIZE個(gè)小塊內(nèi)存,串起來(lái)。
            freeMemHeader[i].next = &freeMemHeader[i + 1];
        }
        freeMemHeader[POOL_SIZE - 1].next = nullptr;
    }
    p = freeMemHeader;//取內(nèi)存池(鏈表)的頭部,分配給要實(shí)例化的對(duì)象
    cout << "Info:從內(nèi)存池中取了" << size << "字節(jié)的內(nèi)存。" << endl;
    freeMemHeader = freeMemHeader -> next;//從內(nèi)存池中刪去取出的那一小塊地址,即更新內(nèi)存池
    p -> next = nullptr;
    return p;
}
void DemoClass::operator delete(void* p){
    DemoClass* tmp = (DemoClass*) p;
    tmp -> next = freeMemHeader;
    freeMemHeader = tmp;
}

測(cè)試代碼如下:

int main(int argc, char* argv[]){
    cout << "sizeof(DemoClass):" << sizeof(DemoClass) << endl;
    size_t N = 32;
    DemoClass* demos[N];
    for(int i = 0; i < N; ++i){
        demos[i] = new DemoClass(i);
        cout << "address of the ith demo:" << demos[i] << endl;
        cout << endl;
    }
    return 0;
}

其結(jié)果如下:

 

可以看到每個(gè)DemoClass的實(shí)例大小為24字節(jié),內(nèi)存池一次從操作系統(tǒng)中申請(qǐng)了576個(gè)字節(jié)的內(nèi)存,這些內(nèi)存可以容納24個(gè)實(shí)例。上面顯示出了每個(gè)實(shí)例的內(nèi)存地址,內(nèi)存池中相鄰實(shí)例的內(nèi)存首地址之差為24,即實(shí)例的大小,證明了一個(gè)內(nèi)存池的實(shí)例之間確實(shí)沒(méi)有cookie。

當(dāng)內(nèi)存池中內(nèi)存用完后,又向操作系統(tǒng)申請(qǐng)了576個(gè)字節(jié)的內(nèi)存。

由此,只有每個(gè)內(nèi)存池兩側(cè)有cookie,而內(nèi)存池中的實(shí)例不存在cookie,相比于每次調(diào)用new expression實(shí)例化對(duì)象都有cookie,內(nèi)存池的組織形式確實(shí)在形式上提高了內(nèi)存利用率。

那么,有什么問(wèn)題么?

sizeof(DemoClass)等于24

  • int data數(shù)據(jù)域占4個(gè)字節(jié)
  • 兩個(gè)構(gòu)造函數(shù)一個(gè)析構(gòu)函數(shù)各占4字節(jié),共12字節(jié)
  • 額外的指針DemoClass*,在64位機(jī)器上,占8個(gè)字節(jié)

這樣一個(gè)DemoClass的大小確實(shí)是24字節(jié)。wait,what?

我們?yōu)榱私鉀Qcookie帶來(lái)的內(nèi)存浪費(fèi),引入了指針next,但卻又引入了8個(gè)字節(jié)的overhead,脫褲子放屁,多此一舉?

這樣看來(lái)確實(shí)沒(méi)有達(dá)到要求,但至少為我們提供了一種思路,不是么?

3、分時(shí)復(fù)用改進(jìn)方案

首先我們先回憶下c++ 中的Union:

在任意時(shí)刻,聯(lián)合中只能有一個(gè)數(shù)據(jù)成員可以有值。當(dāng)給聯(lián)合中某個(gè)成員賦值之后,該聯(lián)合中的其它成員就變成未定義狀態(tài)了。

結(jié)合我們之前不成熟的內(nèi)存池,我們發(fā)現(xiàn),當(dāng)內(nèi)存池中的桶還沒(méi)有被分配給實(shí)例時(shí),只有next域有用,而當(dāng)桶被分配給實(shí)例后,next域就沒(méi)什么用了;當(dāng)桶被回收時(shí),數(shù)據(jù)域變無(wú)用而next指針又需要用到。這不正是union的特性么?

看一下代碼實(shí)現(xiàn):

#include <iostream>
using namespace std;
class DemoClass{
public:
    DemoClass() = default;
    DemoClass(int i, double p){
        data.num = i;
        data.price = p;
    }
    static void* operator new(size_t size);
    static void operator delete(void *);
    virtual ~DemoClass(){}
private:
    struct DemoData{
        int num;
        double price;
    };
private:
    static DemoClass *freeMemHeader;
    static const size_t POOL_SIZE;
    union {
        DemoClass *next;
        DemoData data;
    };
    
};
DemoClass * DemoClass::freeMemHeader = nullptr;
const size_t DemoClass::POOL_SIZE = 24;//設(shè)定內(nèi)存池能容納24個(gè)DemoClass對(duì)象
void* DemoClass::operator new(size_t size){
    DemoClass* p;
    if(!freeMemHeader){//freeMemHeader為空,內(nèi)存池中無(wú)空間,分配內(nèi)存
        size_t pool_mem_bytes = size * POOL_SIZE;//內(nèi)存池的字節(jié)大小 = 每個(gè)實(shí)例的大?。ㄗ止?jié)數(shù))* 內(nèi)存池中能容納的最大實(shí)例數(shù)
        freeMemHeader = reinterpret_cast<DemoClass*>(new char[pool_mem_bytes]);//new char[]分配pool_mem_bytes個(gè)字節(jié),因?yàn)槊總€(gè)char占用1個(gè)字節(jié)
        cout << "Info:向操作系統(tǒng)申請(qǐng)了" << pool_mem_bytes << "字節(jié)的內(nèi)存。" << endl;
        for(int i = 0;i < POOL_SIZE - 1; ++i){//將內(nèi)存池中POOL_SIZE個(gè)小塊內(nèi)存,串起來(lái)。
            freeMemHeader[i].next = &freeMemHeader[i + 1];
        }
        freeMemHeader[POOL_SIZE - 1].next = nullptr;
    }
    p = freeMemHeader;//取內(nèi)存池(鏈表)的頭部,分配給要實(shí)例化的對(duì)象
    cout << "Info:從內(nèi)存池中取了" << size << "字節(jié)的內(nèi)存。" << endl;
    freeMemHeader = freeMemHeader -> next;//從內(nèi)存池中刪去取出的那一小塊地址,即更新內(nèi)存池
    p -> next = nullptr;
    return p;
}
void DemoClass::operator delete(void* p){
    DemoClass* tmp = (DemoClass*) p;
    tmp -> next = freeMemHeader;
    freeMemHeader = tmp;
}

對(duì)比前一種實(shí)現(xiàn)代碼,只是構(gòu)造函數(shù)、數(shù)據(jù)域和指針域的組織形式發(fā)生了變化:

  • 由于數(shù)據(jù)域增加了price項(xiàng),構(gòu)造函數(shù)中也增加了對(duì)應(yīng)的參數(shù)
  • 數(shù)據(jù)域被集成定義成一個(gè)類(lèi)自定義struct類(lèi)型
  • 數(shù)據(jù)域和指針域被組織為union

測(cè)試代碼依舊:

int main(int argc, char* argv[]){
    cout << "sizeof(DemoClass):" << sizeof(DemoClass) << endl;
    size_t N = 32;
    DemoClass* demos[N];
    for(int i = 0; i < N; ++i){
        demos[i] = new DemoClass(i, i * i);
        cout << "address of the " << i << "th demo:" << demos[i] << endl;
        cout << endl;
    }
    return 0;
}

結(jié)果:

 

 

可以看到每個(gè)DemoClass的實(shí)例大小為24字節(jié),一個(gè)內(nèi)存池的實(shí)例之間沒(méi)有cookie。

分析一下sizeof(DemoClass)等于24的緣由:

data數(shù)據(jù)域占12個(gè)字節(jié)(int 4字節(jié)、double 8字節(jié))。
兩個(gè)構(gòu)造函數(shù)一個(gè)析構(gòu)函數(shù)各占4字節(jié),共12字節(jié)。
指針DemoClass,在64位機(jī)器上,占8個(gè)字節(jié),但由于和數(shù)據(jù)域使用了union,data數(shù)據(jù)域12個(gè)字節(jié)中的前8個(gè)字節(jié)在適當(dāng)?shù)臅r(shí)機(jī)被看作DemoClass,而不占用額外空間,消除了overhead。
這樣一個(gè)DemoClass的大小確實(shí)是24字節(jié)。利用union的分時(shí)復(fù)用特性,我們消除了初步方案中指針帶來(lái)的脫褲子放屁效果。

4、其他的思考

細(xì)心的讀者可能會(huì)發(fā)現(xiàn),前面的那兩種方案都有共同的小缺陷,即當(dāng)程序一直實(shí)例化而不析構(gòu)時(shí),內(nèi)存池會(huì)向操作系統(tǒng)申請(qǐng)多次大塊內(nèi)存,而當(dāng)這些對(duì)象一起回收時(shí),內(nèi)存池中的剩余桶數(shù)會(huì)遠(yuǎn)大于設(shè)定的POOL_SIZE的大小,這個(gè)峰值多大取決于類(lèi)實(shí)例化和回收的時(shí)機(jī)。

另外,內(nèi)存池中的內(nèi)存暫時(shí)不會(huì)回收給操作系統(tǒng),峰值很大可能會(huì)對(duì)內(nèi)存分配帶來(lái)一些影響,不過(guò)這卻不屬于內(nèi)存泄漏。在以后的文章中,我們可能會(huì)討論一些性能更好的內(nèi)存分配方案。

以上就是C++內(nèi)存池兩種方案對(duì)比的詳細(xì)內(nèi)容,更多關(guān)于C++內(nèi)存池的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!望大家以后多多支持腳本之家!

相關(guān)文章

  • C++標(biāo)準(zhǔn)模板庫(kù)string類(lèi)的介紹與使用講解

    C++標(biāo)準(zhǔn)模板庫(kù)string類(lèi)的介紹與使用講解

    今天小編就為大家分享一篇關(guān)于C++標(biāo)準(zhǔn)模板庫(kù)string類(lèi)的介紹與使用講解,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2018-12-12
  • C語(yǔ)言實(shí)現(xiàn)BMP圖像處理(彩色圖轉(zhuǎn)灰度圖)

    C語(yǔ)言實(shí)現(xiàn)BMP圖像處理(彩色圖轉(zhuǎn)灰度圖)

    這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)BMP圖像處理,彩色圖轉(zhuǎn)灰度圖,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-10-10
  • C++用new創(chuàng)建對(duì)象和不用new創(chuàng)建對(duì)象的區(qū)別解析

    C++用new創(chuàng)建對(duì)象和不用new創(chuàng)建對(duì)象的區(qū)別解析

    在C++用new創(chuàng)建對(duì)象和不用new創(chuàng)建對(duì)象是有區(qū)別的,不知你是否清楚的了解它們到底有什么樣的區(qū)別呢?下面小編就用示例來(lái)告訴大家吧,需要的朋友可以過(guò)來(lái)參考下
    2013-07-07
  • Objective-C限制函數(shù)調(diào)用的頻率詳解

    Objective-C限制函數(shù)調(diào)用的頻率詳解

    這篇文章主要給大家介紹了關(guān)于Objective-C限制函數(shù)調(diào)用的頻率的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。
    2017-12-12
  • Ubuntu配置sublime text 3的c編譯環(huán)境的具體步驟

    Ubuntu配置sublime text 3的c編譯環(huán)境的具體步驟

    下面小編就為大家?guī)?lái)一篇Ubuntu配置sublime text 3的c編譯環(huán)境的具體步驟。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-03-03
  • C++高級(jí)數(shù)據(jù)結(jié)構(gòu)之并查集

    C++高級(jí)數(shù)據(jù)結(jié)構(gòu)之并查集

    這篇文章主要介紹了C高級(jí)數(shù)據(jù)結(jié)構(gòu)之并查集,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下
    2022-05-05
  • 你真的懂C++中的namespace用法

    你真的懂C++中的namespace用法

    命名空間(namespace)為防止名字沖突提供了更加可控的機(jī)制,命名空間分割了全局命名空間,其中每個(gè)命名空間是一個(gè)作用域,今天通過(guò)本文給大家分享C++中namespace用法,感興趣的朋友一起看看吧
    2021-06-06
  • vscode配置遠(yuǎn)程開(kāi)發(fā)環(huán)境并遠(yuǎn)程調(diào)試運(yùn)行C++代碼的教程

    vscode配置遠(yuǎn)程開(kāi)發(fā)環(huán)境并遠(yuǎn)程調(diào)試運(yùn)行C++代碼的教程

    這篇文章主要介紹了vscode配置遠(yuǎn)程開(kāi)發(fā)環(huán)境并遠(yuǎn)程調(diào)試運(yùn)行C++代碼的教程,本文通過(guò)截圖實(shí)例相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-04-04
  • C語(yǔ)言實(shí)現(xiàn)循環(huán)雙鏈表

    C語(yǔ)言實(shí)現(xiàn)循環(huán)雙鏈表

    這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)循環(huán)雙鏈表,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-11-11
  • C++中全局變量的初始化全過(guò)程

    C++中全局變量的初始化全過(guò)程

    這篇文章主要介紹了C++全局變量的初始化全過(guò)程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-08-08

最新評(píng)論