C++ 實現(xiàn)高性能內(nèi)存池

簡介


主要是對開源代碼的學(xué)習(xí)。按自己的理解把代碼重寫了一遍诵姜,添加了中文注釋搏熄。下面放鏈接。

  1. 原始項目
  2. 我的項目

原理


一般而言宵凌,我們使用 malloc 或者 new 來動態(tài)管理內(nèi)存瞎惫。 然而译株,這些函數(shù)由于牽涉到系統(tǒng)調(diào)用,效率很低乘寒。于是伞辛,如果我們使用很少的系統(tǒng)調(diào)用夯缺,申請一大塊內(nèi)存自己管理,就非常好了瞧捌。這就是內(nèi)存池的思想。內(nèi)存池向系統(tǒng)申請大塊內(nèi)存殿怜,然后再分為小片給程序使用头谜。每次我們請求內(nèi)存鸠澈,其實是在內(nèi)存池里拿而非通過系統(tǒng)調(diào)用。它的優(yōu)勢體現(xiàn)在:

  1. 它從原理上比 malloc 以及 new
  2. 分配內(nèi)存時不存在 overhead
  3. 幾乎不存在內(nèi)存碎片
  4. 無須一個一個釋放對象际度,只需要釋放內(nèi)存池即可

內(nèi)存池也有缺陷:

  1. 需要知道對象的大小
  2. 需要根據(jù)應(yīng)用的情況來調(diào)節(jié)

源碼閱讀筆記

= delete 用法

  /*******
  * 賦值 *
  *******/
  //  使用 = delete 禁用拷貝賦值乖菱,只保留移動賦值
  MemoryPool& operator=(const MemoryPool& mp) = delete;
  MemoryPool& operator=(const MemoryPool&& mp) noexcept;

參考 stackoverflow 上的相關(guān)問題蓬网。一句話總結(jié)就是禁用函數(shù)帆锋。
以上代碼實際上就是禁用拷貝賦值,僅使用移動賦值皮官。這是顯然的哲鸳,內(nèi)存池拷貝代價太大。

static_assert 用法

  // static_assert: 編譯期斷言檢查
  static_assert(BlockSize >= 2 * sizeof(Slot_), "BlockSize too small.")

在編譯期間執(zhí)行檢查讯沈,如果不通過條件 arg1缺狠,則報錯 arg2萍摊。
由于是編譯期間運行,因此只能用于編譯期間就確定了的常量穷劈。尤其常用于模板類的檢查。

使用 union 的理由

    union Slot_ {
      value_type element;
      Slot_* next;
    };

一個奇怪的構(gòu)造函數(shù)

// MemoryPool.h
  MemoryPool(const MemoryPool& mp) noexcept; // 拷貝構(gòu)造函數(shù)

// MemoryPool.cpp
  template <typename T, size_t BlockSize>
  MemoryPool<T, BlockSize>::MemoryPool(const MemoryPool& mp) noexcept:
  MemoryPool() {
  }

推測其意思就是調(diào)用無參數(shù)構(gòu)造函數(shù)社证。

析構(gòu)函數(shù)中的強制類型轉(zhuǎn)換

template <typename T, size_t BlockSize>
MemoryPool<T, BlockSize>::~MemoryPool()
noexcept
{
  slot_pointer_ curr = currentBlock_;
  while (curr != nullptr) {
    slot_pointer_ prev = curr->next;
    operator delete(reinterpret_cast<void*>(curr));
    curr = prev;
  }
}

比較費解為什么要強制轉(zhuǎn)為 void* 指針追葡。源碼中多次使用了 reinterpret_cast奕短,具體可以參考 static_cast 和 reinterpret_cast

內(nèi)存池實現(xiàn)細(xì)節(jié)


直接上圖谬返,這個圖說明了內(nèi)存池的運作原理:

Memory-Pool

首先定義了一個 union朱浴,非常值得注意的是达椰,這不是一個鏈表啰劲。這個 union 的本質(zhì)是復(fù)用檀何,一個 slot 里,既可能存的是數(shù)據(jù)栓辜,也可能存的是指向另一個 slot 的指針藕甩。這樣的設(shè)計有助于節(jié)省空間周荐,在某個對象不再需要時,直接放入 freeSlots_ 鏈表中腋妙,并改寫 next 指針讯榕。

  union Slot_ {
    T element;
    Slot_ *next;
  };

為了實現(xiàn)內(nèi)存池匙睹,我們需要四個指針:

  Slot_ *currentBlock_;
  Slot_ *currentSlot_;
  Slot_ *lastSlot_;
  Slot_ *freeSlots_;  // 這是一個比較讓人疑惑的名字垃僚,實際是指的 deallocate 之后空閑出來的 slots

結(jié)合上面圖中的位置谆棺,可以大概理解其運作方式罕袋。注意圖中沒有標(biāo)出 freeSlots_,因為這個指針如果沒有進(jìn)行 deallocate朵夏,就保持為 nullptr仰猖。

下面分析最關(guān)鍵的兩個函數(shù)奈籽。

  1. 內(nèi)存池向系統(tǒng)申請內(nèi)存
template <typename T, size_t BlockSize>
void MemoryPool<T, BlockSize>::allocateBlock() {
  // 頭插鏈表
  // A->B->C 插入 X
  // X->A->B->C
  char *newBlock = reinterpret_cast<char *>(::operator new(BlockSize));
  reinterpret_cast<Slot_ *>(newBlock)->next = currentBlock_;
  currentBlock_ = reinterpret_cast<Slot_ *>(newBlock);
  // 這里需要作一個內(nèi)存對齊,因為第一個區(qū)域存放的是指向上一個 Block 的指針躏升,而不是 Slot
  // 而之后的都是Slot
  char *body = newBlock + sizeof(Slot_ *);
  size_t bodyPadding = padPointer(body, alignof(Slot_));
  currentSlot_ = reinterpret_cast<Slot_ *>(body + bodyPadding);

  lastSlot_ =
      reinterpret_cast<Slot_ *>(newBlock + BlockSize - sizeof(Slot_) + 1);
}

里面的注意點都用中文注釋出來了狼忱。

  1. 在內(nèi)存池中為對象分配內(nèi)存
  inline T *allocate(size_t n = 1, const T *hint = nullptr) {
    if (freeSlots_ != nullptr) {
      // 存在 freeSlots
      T *result = reinterpret_cast<T *>(freeSlots_);
      freeSlots_ = freeSlots_->next;
      return result;
    } else {
      // freeSlots 還未初始化或者已經(jīng)耗盡
      if (currentSlot_ >= lastSlot_) {
        allocateBlock();
      }
      return reinterpret_cast<T *>(currentSlot_++);
    }
  }

這里尤其需要注意的是钻弄,在本文的實現(xiàn)中,freeSlots_只在釋放對象時才會被賦值饲帅。如果只進(jìn)行了分配沒有釋放批销,那么會一直執(zhí)行 else 中的代碼。

  inline void deallocate(T *p, size_t = 1) {
    if (p != nullptr) {
      // 也是頭插鏈表
      // 空閑鏈表 A->B->C丘逸,現(xiàn)在釋放了 X 的空間掀宋,需要將 X 加入空閑鏈表
      // X->A->B->C
      reinterpret_cast<Slot_ *>(p)->next = freeSlots_;
      freeSlots_ = reinterpret_cast<Slot_ *>(p);
    }
  }

在釋放中仲锄,體現(xiàn)了 union 的作用儒喊。傳入的指針本來指向一個對象币呵,將其強制轉(zhuǎn)為 union Slot,再將 next 指針賦值為空閑鏈表頭芯义。此時就覆蓋對象的前 8 字節(jié)內(nèi)容(64 位機(jī)器)妻柒,對象失效。只留下前 8 字節(jié)指針被使用绑警。

內(nèi)存池的效果


運行測試函數(shù)可以得到:

Copyright (c) 2013 Cosku Acay, http://www.coskuacay.com
Provided to compare the default allocator to MemoryPool.

Default Allocator Time: 7.3624

MemoryPool Allocator Time: 1.23673

Here is a secret: the best way of implementing a stack is a dynamic array.
Vector Time: 2.14202

The vector implementation will probably be faster.

可以看出计盒,在 OS X 下,明顯快于原始的 allocator章郁。奇怪的是我在 Linux 操作系統(tǒng)下并沒有跑出這么大的差距,并且 vector 實現(xiàn)確實快于內(nèi)存池聊替。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末惹悄,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子泣港,更是在濱河造成了極大的恐慌当纱,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晨横,死亡現(xiàn)場離奇詭異,居然都是意外死亡手形,警方通過查閱死者的電腦和手機(jī)库糠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門瞬欧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人豫尽,你說我怎么就攤上這事顷帖。” “怎么了榴嗅?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵嗽测,是天一觀的道長。 經(jīng)常有香客問我唠粥,道長晤愧,這世上最難降的妖魔是什么蛉腌? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮烙丛,結(jié)果婚禮上河咽,老公的妹妹穿的比我還像新娘。我一直安慰自己库北,他們只是感情好们陆,可當(dāng)我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著垃你,像睡著了一般。 火紅的嫁衣襯著肌膚如雪惜颇。 梳的紋絲不亂的頭發(fā)上凌摄,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天锨亏,我揣著相機(jī)與錄音,去河邊找鬼器予。 笑死乾翔,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的萌丈。 我是一名探鬼主播勾习,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼巧婶,長吁一口氣:“原來是場噩夢啊……” “哼艺栈!你這毒婦竟也來了湿右?” 一聲冷哼從身側(cè)響起罚勾,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤吭狡,失蹤者是張志新(化名)和其女友劉穎划煮,沒想到半個月后缔俄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年雪情,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片太抓。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡走敌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出跌榔,到底是詐尸還是另有隱情捶障,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站锭部,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏取胎。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一匪傍、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧觉痛,春花似錦役衡、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至盗尸,卻和暖如春柑船,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背泼各。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工鞍时, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人扣蜻。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓逆巍,卻偏偏與公主長得像,于是被迫代替她去往敵國和親莽使。 傳聞我的和親對象是個殘疾皇子锐极,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容