leveldb源碼學(xué)習(xí)--Arena內(nèi)存池實現(xiàn)

一. 內(nèi)存池的優(yōu)勢

1. 直接使用系統(tǒng)調(diào)用的弊端

  • 調(diào)用malloc/new,系統(tǒng)需要根據(jù)“最先匹配”零抬、“最優(yōu)匹配”或其他算法在內(nèi)存空閑塊表中查找一塊空閑內(nèi)存立美,調(diào)用free/delete,系統(tǒng)可能需要合并空閑內(nèi)存塊越锈,這些會產(chǎn)生額外開銷
  • 頻繁使用時會產(chǎn)生大量內(nèi)存碎片仅淑,從而降低程序運行效率(對于內(nèi)存碎片問題上這篇博文有著不一樣的看法晕拆,有一定道理萝风,可以參考一下_)
  • 容易造成內(nèi)存泄漏

2. 內(nèi)存池的優(yōu)點

  • 比malloc/free進行內(nèi)存申請/釋放的方式快(向內(nèi)存池請求嘀掸,而不是直接向操作系統(tǒng)請求)
  • 不會產(chǎn)生或很少產(chǎn)生堆碎片
  • 可避免內(nèi)存泄漏

二. Arena內(nèi)存池的實現(xiàn)

1. Arena類的成員變量

// Allocation state
char* alloc_ptr_;
size_t alloc_bytes_remaining_;
 
// Array of new[] allocated memory blocks
std::vector<char*> blocks_;

// Total memory usage of the arena.
port::AtomicPointer memory_usage_;

Arena類的成員變量還是相對比較簡單的,alloc_ptr_表示當(dāng)前內(nèi)存塊(block)偏移量指針规惰,也就是未使用內(nèi)存的首地址睬塌。 alloc_bytes_remaining_表示當(dāng)前塊所未使用的空間大小。blocks_是一個vector用來存儲每一次向系統(tǒng)請求的分配的內(nèi)存指針歇万。 memory_usage_則是用來記錄Arena類內(nèi)存使用情況的揩晴。

2. Arena類的成員函數(shù)

a. 構(gòu)造函數(shù)

Arena::Arena() : memory_usage_(0) {
  alloc_ptr_ = NULL;  // First allocation will allocate a block
  alloc_bytes_remaining_ = 0;
}

非常簡單的一個構(gòu)造函數(shù),負責(zé)初始化一些變量贪磺,注意剛開始是沒有立刻分配內(nèi)存的

b. 析構(gòu)函數(shù)

Arena::~Arena() {
  for (size_t i = 0; i < blocks_.size(); i++) {
    delete[] blocks_[i];
  }
}

析構(gòu)函數(shù)負責(zé)將從系統(tǒng)中申請的內(nèi)存返還給系統(tǒng)硫兰。

c. Allocate() && AllocateFallback() && AllocateNewBlock()

inline char* Arena::Allocate(size_t bytes) {
  // The semantics of what to return are a bit messy if we allow
  // 0-byte allocations, so we disallow them here (we don't need
  // them for our internal use).
  assert(bytes > 0);
  if (bytes <= alloc_bytes_remaining_) {
    char* result = alloc_ptr_;
    alloc_ptr_ += bytes;
    alloc_bytes_remaining_ -= bytes;
    return result;
  }
  return AllocateFallback(bytes);
}

char* Arena::AllocateFallback(size_t bytes) {
  if (bytes > kBlockSize / 4) {
    // Object is more than a quarter of our block size.  Allocate it separately
    // to avoid wasting too much space in leftover bytes.
    char* result = AllocateNewBlock(bytes);
    return result;
  }

  // We waste the remaining space in the current block.
  alloc_ptr_ = AllocateNewBlock(kBlockSize);
  alloc_bytes_remaining_ = kBlockSize;

  char* result = alloc_ptr_;
  alloc_ptr_ += bytes;
  alloc_bytes_remaining_ -= bytes;
  return result;
}

char* Arena::AllocateNewBlock(size_t block_bytes) {
  char* result = new char[block_bytes];
  blocks_.push_back(result);
  memory_usage_.NoBarrier_Store(
      reinterpret_cast<void*>(MemoryUsage() + block_bytes + sizeof(char*)));
  return result;
}

Allocate是Arena向外界提供的接口,該函數(shù)會調(diào)用AllocateFallback() && AllocateNewBlock() 這兩個私有函數(shù)寒锚。

  1. 如果需求的內(nèi)存小于剩余的內(nèi)存瞄崇,那么直接從內(nèi)存池中獲取
  2. 如果需求的內(nèi)存大于剩余的內(nèi)存,而且大于1K壕曼,則給這內(nèi)存單獨分配一塊bytes(函數(shù)參數(shù))大小的內(nèi)存
  3. 如果需求的內(nèi)存大于剩余的內(nèi)存苏研,而且小于4096/4,則重新分配一個內(nèi)存塊腮郊,默認大小4096摹蘑,用于存儲數(shù)據(jù)。原有的剩余空間浪費掉轧飞。

d. AllocateAligned()

char* Arena::AllocateAligned(size_t bytes) {
  const int align = (sizeof(void*) > 8) ? sizeof(void*) : 8;
  assert((align & (align-1)) == 0);   // Pointer size should be a power of 2
  size_t current_mod = reinterpret_cast<uintptr_t>(alloc_ptr_) & (align-1);
  size_t slop = (current_mod == 0 ? 0 : align - current_mod);
  size_t needed = bytes + slop;
  char* result;
  if (needed <= alloc_bytes_remaining_) {
    result = alloc_ptr_ + slop;
    alloc_ptr_ += needed;
    alloc_bytes_remaining_ -= needed;
  } else {
    // AllocateFallback always returned aligned memory
    result = AllocateFallback(bytes);
  }
  assert((reinterpret_cast<uintptr_t>(result) & (align-1)) == 0);
  return result;
}

Arena還提供了字節(jié)對齊內(nèi)存分配衅鹿,一般情況是8個字節(jié)對齊分配撒踪。對齊內(nèi)存的好處簡單的說就是加速內(nèi)存訪問。具體實現(xiàn)源碼已經(jīng)寫的非常詳細了大渤,稍微有點疑惑的地方也都有了注釋制妄。

e. MemoryUsage()

size_t MemoryUsage() const {
    return reinterpret_cast<uintptr_t>(memory_usage_.NoBarrier_Load());
  }

Arena最后一個對外接口是返回這個內(nèi)存池分配總的內(nèi)存大小。

icecity96 liuhiter@gmail.com

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末泵三,一起剝皮案震驚了整個濱河市耕捞,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌烫幕,老刑警劉巖俺抽,帶你破解...
    沈念sama閱讀 218,640評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異较曼,居然都是意外死亡磷斧,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評論 3 395
  • 文/潘曉璐 我一進店門捷犹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來弛饭,“玉大人,你說我怎么就攤上這事萍歉『⒀疲” “怎么了?”我有些...
    開封第一講書人閱讀 165,011評論 0 355
  • 文/不壞的土叔 我叫張陵翠桦,是天一觀的道長。 經(jīng)常有香客問我胳蛮,道長销凑,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,755評論 1 294
  • 正文 為了忘掉前任仅炊,我火速辦了婚禮斗幼,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘抚垄。我一直安慰自己蜕窿,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,774評論 6 392
  • 文/花漫 我一把揭開白布呆馁。 她就那樣靜靜地躺著桐经,像睡著了一般。 火紅的嫁衣襯著肌膚如雪浙滤。 梳的紋絲不亂的頭發(fā)上阴挣,一...
    開封第一講書人閱讀 51,610評論 1 305
  • 那天,我揣著相機與錄音纺腊,去河邊找鬼畔咧。 笑死茎芭,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的誓沸。 我是一名探鬼主播梅桩,決...
    沈念sama閱讀 40,352評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼拜隧!你這毒婦竟也來了宿百?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,257評論 0 276
  • 序言:老撾萬榮一對情侶失蹤虹蓄,失蹤者是張志新(化名)和其女友劉穎犀呼,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體薇组,經(jīng)...
    沈念sama閱讀 45,717評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡外臂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,894評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了律胀。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宋光。...
    茶點故事閱讀 40,021評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖炭菌,靈堂內(nèi)的尸體忽然破棺而出罪佳,到底是詐尸還是另有隱情,我是刑警寧澤黑低,帶...
    沈念sama閱讀 35,735評論 5 346
  • 正文 年R本政府宣布赘艳,位于F島的核電站,受9級特大地震影響克握,放射性物質(zhì)發(fā)生泄漏蕾管。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,354評論 3 330
  • 文/蒙蒙 一菩暗、第九天 我趴在偏房一處隱蔽的房頂上張望掰曾。 院中可真熱鬧,春花似錦停团、人聲如沸旷坦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽秒梅。三九已至,卻和暖如春舌胶,著一層夾襖步出監(jiān)牢的瞬間番电,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留漱办,地道東北人这刷。 一個月前我還...
    沈念sama閱讀 48,224評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像娩井,于是被迫代替她去往敵國和親暇屋。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,974評論 2 355

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