leveldb Arena 分析

leveldb Arena 分析


版權聲明:本文為 cheng-zhi 原創(chuàng)文章儿倒,可以隨意轉載,但必須在明確位置注明出處斗锭!

Arena

Arenaleveldb 項目里面使用的輕量級的內(nèi)存池對象枣购,leveldb 用這個對象來管理內(nèi)存的分配,簡化了 newdelete 的調(diào)用颠放,我們也可以從這個輕量級的內(nèi)存池對象學習 google 大神工程師是如何管理內(nèi)存的。

Arena 內(nèi)存管理模型

這是羅道文網(wǎng)站上關于 leveldb 的一張 Arena 的內(nèi)存模型圖:

Arena 使用下面幾個成員變量來描述上面的模型圖

// 當前內(nèi)存塊未分配內(nèi)存的起始地址
char* alloc_ptr_;

// 當前內(nèi)存塊剩余的內(nèi)存
size_t alloc_bytes_remaining_;

// Arena 使用 vector 來存儲每個內(nèi)存塊的地址
std::vector<char*> blocks_;

// 當前 Arena 已經(jīng)分配的總內(nèi)存量 
port::AtomicPointer memory_usage_;

在開始分析之前你需要了解申請內(nèi)存分配內(nèi)存的區(qū)別:

  1. 申請內(nèi)存:使用 new 來向操作系統(tǒng)申請一塊連續(xù)的內(nèi)存區(qū)域吭敢。
  2. 分配內(nèi)存:將已經(jīng)申請的內(nèi)存分配給項目組件使用碰凶,這體現(xiàn)在增加 alloc_ptr_ 和減少 alloc_bytes_remaining_ 這兩個指針上。

為什么要強調(diào)這兩個概念呢鹿驼?

因為 Arena 是一個內(nèi)存池欲低,他的功能就是內(nèi)存的管理,包括申請內(nèi)存蠢沿,分配內(nèi)存伸头,釋放內(nèi)存

Arena 的構造和析構

Arena 的源碼位置:/leveldb/util/arena.h, /leveldb/util/arena.cc

// 良好的初始化風格
Arena::Arena() : memory_usage_(0) {
  alloc_ptr_ = NULL;  
  alloc_bytes_remaining_ = 0;
}

Arena::~Arena() {
  // 分別釋放 vector 中每個指針指向的內(nèi)存塊
  for (size_t i = 0; i < blocks_.size(); i++) {
    delete[] blocks_[i];
  }
}

Arena 提供的接口函數(shù)

Arena 給我們提供了 3 個 public 函數(shù)來簡化我們的內(nèi)存訪問

public:
  // 基本的內(nèi)存分配函數(shù)
  char* Allocate(size_t bytes);

  // 按照字節(jié)對齊來分配內(nèi)存
  char* AllocateAligned(size_t bytes);

  // 返回目前分配的總的內(nèi)存
  size_t MemoryUsage() const;

下面一一分析

Allocate

這是一個最重要的內(nèi)存分配函數(shù)舷蟀,這個函數(shù)會根據(jù)你要申請的內(nèi)存大小來調(diào)用另外兩個私有的內(nèi)存分配函數(shù)

inline char* Arena::Allocate(size_t bytes) {
  // 不需要分配 0 字節(jié)的內(nèi)存
  assert(bytes > 0);

  // 申請的內(nèi)存小于剩余的內(nèi)存,就直接在當前內(nèi)存塊上分配內(nèi)存
  if (bytes <= alloc_bytes_remaining_) {
    char* result = alloc_ptr_;
    alloc_ptr_ += bytes;
    alloc_bytes_remaining_ -= bytes;
    return result;
  }

  // 申請的內(nèi)存的大于當前內(nèi)存塊剩余的內(nèi)存,就用這個函數(shù)來重新申請內(nèi)存
  return AllocateFallback(bytes);
}

AllocateFallback

在申請的內(nèi)存大于當前塊剩余內(nèi)存時野宜,AllocateFallback會被調(diào)用扫步,它提供 2 種申請內(nèi)存的策略:

  1. 當前塊剩余內(nèi)存 < 申請的內(nèi)存 < 默認內(nèi)存塊大小的 1 / 4 (1096 KB / 4 = 1024 KB),重新申請一個默認大小的內(nèi)存塊 (4096 KB)匈子。
  2. 申請的內(nèi)存大于當前塊剩余內(nèi)存河胎,并且大于默認內(nèi)存塊大小的 1 / 4,直接申請一個需要的的 bytes 大小的內(nèi)存塊虎敦。

第二種分配方法的理由是可以減少內(nèi)存分配的次數(shù)游岳,分析如下:

假如我當前塊剩余 900 KB 內(nèi)存,而我需要申請 1200 KB 內(nèi)存其徙,如果我直接申請一塊大小為 1200 KB 的內(nèi)存塊胚迫,就只需要申請一次;但是如果我在當前塊先分配 900 KB 內(nèi)存唾那,然后再申請一個新的 4096 KB 的內(nèi)存塊访锻,再在里面分配 1200 - 900 = 300 KB 的內(nèi)存,這樣就需要申請 1 次內(nèi)存闹获,分配兩次內(nèi)存期犬,效率低下了不少(在分配比較頻繁的時候),因此這樣直接申請并分配一個 bytes 大小的內(nèi)存塊非常高效方便避诽。

// bytes 代表實際要申請的內(nèi)存大小
char* Arena::AllocateFallback(size_t bytes) {
  // kBlockSize = 4096 KB
  // bytes > 1 / 4 (1024 KB)龟虎,調(diào)用 AllocateNewBlock 申請一塊新的大小為 bytes 的內(nèi)存
  if (bytes > kBlockSize / 4) {
    char* result = AllocateNewBlock(bytes);
    return result;
  }

  // bytes < 1 / 4 (1024 KB),申請一個默認大小為 4096 KB 的內(nèi)存塊
  alloc_ptr_ = AllocateNewBlock(kBlockSize);
  alloc_bytes_remaining_ = kBlockSize;

  // 在申請的默認大小的內(nèi)存塊里分配 bytes 字節(jié)內(nèi)存
  char* result = alloc_ptr_;
  alloc_ptr_ += bytes;
  alloc_bytes_remaining_ -= bytes;
  return result;
}

AllocateNewBlock

這是 AllocateNewBlock 的實現(xiàn)沙庐,它就是簡單的申請了一個 block_bytes 大小的內(nèi)存塊

char* Arena::AllocateNewBlock(size_t block_bytes) {
  // 申請一個 block_bytes 大小的內(nèi)存塊
  char* result = new char[block_bytes];
  
  // 向 vector 中添加這個內(nèi)存塊的地址
  blocks_.push_back(result);

  // 增加當前內(nèi)存分配的總量
  memory_usage_.NoBarrier_Store(
      reinterpret_cast<void*>(MemoryUsage() + block_bytes + sizeof(char*)));
  return result;
}

AllocateAligned

這個函數(shù)可以分配字節(jié)對齊的內(nèi)存遣总,實現(xiàn)稍微有些復雜,不過仔細分析還是有很多營養(yǎng)的轨功,注釋很詳細

char* Arena::AllocateAligned(size_t bytes) {
  // 設置要對齊的字節(jié)數(shù)旭斥,最多 8 字節(jié)對齊,否則就按照當前機器的 void* 的大小來對齊
  const int align = (sizeof(void*) > 8) ? sizeof(void*) : 8;

  // 字節(jié)對齊必須是 2 的次冪, 例如 align = 8
  // 8 & (8 - 1) = 1000 & 0111 = 0, 表示 8 是字節(jié)對齊的
  assert((align & (align-1)) == 0);   
  
  // 了解一個公式:A & (B - 1) = A % B
  // 所以古涧,這句話的意思是將 alloc_ptr_ % align 的值強制轉換成 uintptr_t 類型
  // 這個 uintptr_t 類型代表了當前機器的指針大小
  size_t current_mod = reinterpret_cast<uintptr_t>(alloc_ptr_) & (align-1);
  
  // 如果上面的代碼返回 0 代表當前 alloc_ptr_ 已經(jīng)是字節(jié)對齊了
  // 否則就計算出對齊的偏差
  // 例如 current_mod = 2, 則還需要 8 - 2 = 6 個字節(jié)才能使得 alloc_ptr 按照 8 字節(jié)對齊
  size_t slop = (current_mod == 0 ? 0 : align - current_mod);

  // 分配的字節(jié)數(shù)加上對齊偏差就是最后需要分配的內(nèi)存字節(jié)總量
  size_t needed = bytes + slop;
  char* result;

  // 當總量小于當前內(nèi)存塊的剩余大小垂券,就直接在當前內(nèi)存塊分配 needed 大小的內(nèi)存
  if (needed <= alloc_bytes_remaining_) {
    result = alloc_ptr_ + slop;
    alloc_ptr_ += needed;
    alloc_bytes_remaining_ -= needed;
  } else {
    // 否則就按照這個函數(shù)的內(nèi)存分配策略來申請內(nèi)存,見上面的分析
    result = AllocateFallback(bytes);
  }
  assert((reinterpret_cast<uintptr_t>(result) & (align-1)) == 0);
  return result;
}

總結

總的來說羡滑,Arena 有 3 種內(nèi)存分配策略菇爪,下面申請的內(nèi)存用 bytes 表示:

  1. bytes < 當前塊剩余內(nèi)存 => 直接在當前塊分配。
  2. 當前塊剩余內(nèi)存 < bytes < 1024 KB (默認內(nèi)存塊大小的 1 / 4) => 直接申請一個默認大小為 4096 KB 的內(nèi)存塊柒昏,然后分配內(nèi)存凳宙。
  3. bytes > 當前塊剩余內(nèi)存 && bytes > 1024 KB => 直接申請一個新的大小為 bytes 的內(nèi)存塊,并分配內(nèi)存职祷。

Arena 是一個內(nèi)存池對象氏涩,用來管理 leveldb 的內(nèi)存分配届囚,可以說是整個項目非常重要的模塊了,不可不知 ~

試著了解開源項目的內(nèi)存分配策略

原文地址

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末是尖,一起剝皮案震驚了整個濱河市意系,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌饺汹,老刑警劉巖蛔添,帶你破解...
    沈念sama閱讀 212,332評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異兜辞,居然都是意外死亡迎瞧,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,508評論 3 385
  • 文/潘曉璐 我一進店門逸吵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來凶硅,“玉大人,你說我怎么就攤上這事胁塞∮匠ⅲ” “怎么了?”我有些...
    開封第一講書人閱讀 157,812評論 0 348
  • 文/不壞的土叔 我叫張陵啸罢,是天一觀的道長编检。 經(jīng)常有香客問我,道長扰才,這世上最難降的妖魔是什么允懂? 我笑而不...
    開封第一講書人閱讀 56,607評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮衩匣,結果婚禮上蕾总,老公的妹妹穿的比我還像新娘。我一直安慰自己琅捏,他們只是感情好生百,可當我...
    茶點故事閱讀 65,728評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著柄延,像睡著了一般蚀浆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上搜吧,一...
    開封第一講書人閱讀 49,919評論 1 290
  • 那天市俊,我揣著相機與錄音,去河邊找鬼滤奈。 笑死摆昧,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的蜒程。 我是一名探鬼主播绅你,決...
    沈念sama閱讀 39,071評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼伺帘,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了勇吊?” 一聲冷哼從身側響起曼追,我...
    開封第一講書人閱讀 37,802評論 0 268
  • 序言:老撾萬榮一對情侶失蹤窍仰,失蹤者是張志新(化名)和其女友劉穎汉规,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體驹吮,經(jīng)...
    沈念sama閱讀 44,256評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡针史,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,576評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了碟狞。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片啄枕。...
    茶點故事閱讀 38,712評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖族沃,靈堂內(nèi)的尸體忽然破棺而出频祝,到底是詐尸還是另有隱情,我是刑警寧澤脆淹,帶...
    沈念sama閱讀 34,389評論 4 332
  • 正文 年R本政府宣布常空,位于F島的核電站,受9級特大地震影響盖溺,放射性物質發(fā)生泄漏漓糙。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,032評論 3 316
  • 文/蒙蒙 一烘嘱、第九天 我趴在偏房一處隱蔽的房頂上張望昆禽。 院中可真熱鬧,春花似錦蝇庭、人聲如沸醉鳖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽盗棵。三九已至,卻和暖如春牍蜂,著一層夾襖步出監(jiān)牢的瞬間漾根,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,026評論 1 266
  • 我被黑心中介騙來泰國打工鲫竞, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留辐怕,地道東北人。 一個月前我還...
    沈念sama閱讀 46,473評論 2 360
  • 正文 我出身青樓从绘,卻偏偏與公主長得像寄疏,于是被迫代替她去往敵國和親是牢。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,606評論 2 350

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