1. 程式人生 > >Memcache原始碼閱讀(4)---記憶體管理

Memcache原始碼閱讀(4)---記憶體管理

我看的原始碼版本是1.2.4

memcached的儲存

memcached擁有一個記憶體池,記憶體池中的記憶體分成多種大小的chunk,chunk的大小有一個基礎大小(最小的chunk大小,base chunk size),然後後面大小的是以settings.factor為因子不斷的增大。比如說base chunk size為64byte, setting.factor = 1.5,那麼memcached擁有的chunk size有64byte, 64 * 1.5 = 96byte, 96 * 1.5 = 144byte, … 一直到頁的大小的1/2。

需要儲存資料的時候,從slab中找一個最小匹配的chunk來儲存資料。比如說,我現在有個資料大小是112byte,那麼memcached就會找一個最小的、能容下這個資料的chunk來儲存這個資料(144byte)。

這樣做雖然有記憶體浪費,但是好處是不需要管理記憶體碎片的問題。

slabclass_t的簡圖

memcached_slabclass_simple
注:這個圖是簡化版的slabclass,我將slab_list看為一個管理著chunk的陣列,事實上,他只是管理著多個slab,slab再管理著chunk而已。

如上圖,使用者向slabclass請求記憶體,memcached先會到對應大小到的slabclass_t中檢視slot是否有空閒chunk,如果有的話,就從空閒陣列中分配;沒有,則會從slab中分配一個沒用過的。當用戶釋放slab分配的chunk時,則會將它放到slabclass_t的slot裡。

slab的結構

slab的結構體

typedef struct {
    unsigned int size;      /* sizes of items */ //就是chunk的大小
    unsigned int perslab;   /* how many items per slab */ //每個slab中有多少個chunk,它會管理著一個slab_list,slab_list裡面有多個slab,每個slab管理著perslab個chunk

    void **slots;           /* list of item ptrs */ //指向空閒陣列,回收釋放的chunk
    unsigned int sl_total;  /* size of previous array */
//空閒陣列的大小 unsigned int sl_curr; /* first free slot */ //指向第一個沒有回收chunk的空閒陣列。 void *end_page_ptr; /* pointer to next free item at end of page, or 0 */ unsigned int end_page_free; /* number of items remaining at end of last alloced page */ unsigned int slabs; /* how many slabs were allocated for this class */ void **slab_list; /* array of slab pointers */ unsigned int list_size; /* size of prev array */ unsigned int killing; /* index+1 of dying slab, or zero if none */ } slabclass_t;

memcached_slabclass_detail
這裡藍色的表示已經被使用者使用過的chunk,即使它被使用者釋放,它只會放到slot中,end_page_ptr並不會管理那些申請了的後來被使用者釋放的chunk,end_page_ptr指向第一個還沒被用過的chunk。

slabs初始化

slabs_init(settings.maxbytes, settings.factor);
#define POWER_SMALLEST 1
#define POWER_LARGEST  200
#define POWER_BLOCK 1048576   //1 M
#define CHUNK_ALIGN_BYTES (sizeof(void *))

static slabclass_t slabclass[POWER_LARGEST + 1]; 
static size_t mem_limit = 0;
static size_t mem_malloced = 0;
static int power_largest;

void slabs_init(const size_t limit, const double factor) {
    int i = POWER_SMALLEST - 1;
    unsigned int size = sizeof(item) + settings.chunk_size;  //這裡是對使用者設定的大小加大一個item結構體的大小,因為儲存資料的時候,使用者資料是存在item結構體之後,item結構體記錄著這個資料的一些資訊。

    /* Factor of 2.0 means use the default memcached behavior */
    if (factor == 2.0 && size < 128)
        size = 128;

    mem_limit = limit;
    memset(slabclass, 0, sizeof(slabclass));

    while (++i < POWER_LARGEST && size <= POWER_BLOCK / 2) {
        /* Make sure items are always n-byte aligned */
        //進行位元組對齊
        if (size % CHUNK_ALIGN_BYTES)
            size += CHUNK_ALIGN_BYTES - (size % CHUNK_ALIGN_BYTES);

        slabclass[i].size = size;
        slabclass[i].perslab = POWER_BLOCK / slabclass[i].size;
        size *= factor;
    }

    power_largest = i;
    slabclass[power_largest].size = POWER_BLOCK;
    slabclass[power_largest].perslab = 1;

}

預設情況下這樣就初始化完了,設定好了slabclass的資訊,沒有給slabs真正開闢儲存空間。當用戶向slabclass申請空間的時候,memcached發現slab沒有可用空間再向系統申請空間。那我們看看初始化時到底是如何給slabs分配記憶體空間的,memcached發現slab沒有可用空間時申請記憶體空間的方式跟這個也類似。

初始化時預先分配slabs

//為每個chunk size大小的slab_class分配空間
static void slabs_preallocate (const unsigned int maxslabs) {
    int i;
    unsigned int prealloc = 0;

    for (i = POWER_SMALLEST; i <= POWER_LARGEST; i++) {
        if (++prealloc > maxslabs)
            return;
        do_slabs_newslab(i);
    }
}

//為第i個slab_class分配空間,分配失敗的時候返回0,成功返回1
static int do_slabs_newslab(const unsigned int id) {
    slabclass_t *p = &slabclass[id];
    int len = p->size * p->perslab;
    char *ptr;

    if (mem_limit && mem_malloced + len > mem_limit && p->slabs > 0)
        return 0;
    //grow slab list返回0的時候是失敗
    if (grow_slab_list(id) == 0) return 0;

    ptr = malloc((size_t)len);
    if (ptr == 0) return 0;

    memset(ptr, 0, (size_t)len);
    //指向第一個沒有分配的chunk,slab分配chunk是從slab_list裡順序分配的。
    p->end_page_ptr = ptr;
    p->end_page_free = p->perslab;
    //為每個slab分配一個1M的page
    p->slab_list[p->slabs++] = ptr;
    mem_malloced += len;
    return 1;
}

//為slab_list新增一個slab,需要看看slab_list是否已滿。
//如果滿了,就需要分配更大的slab_list來裝下slabs。如果滿了,重新分配空間失敗返回0。
static int grow_slab_list (const unsigned int id) {
    slabclass_t *p = &slabclass[id];
    if (p->slabs == p->list_size) {
        size_t new_size =  (p->list_size != 0) ? p->list_size * 2 : 16;
        void *new_list = realloc(p->slab_list, new_size * sizeof(void *));
        if (new_list == 0) return 0;
        p->list_size = new_size;
        p->slab_list = new_list;
    }
    return 1;
}

為使用者從slab中找個合適大小的chunk

void *do_slabs_alloc(const size_t size) {
    slabclass_t *p;
    //找到最小的能裝下size的slab
    unsigned int id = slabs_clsid(size);
    p = &slabclass[id];

    //如果slot中又空閒的chunk,那就從slot中取一個
    /* return off our freelist, if we have one */
    if (p->sl_curr != 0)
        return p->slots[--p->sl_curr];
    //否則我們就從slab_list中取一個
    /* if we recently allocated a whole page, return from that */
    if (p->end_page_ptr) {
        void *ptr = p->end_page_ptr;
        if (--p->end_page_free != 0) {
            p->end_page_ptr += p->size;
        } else {
            p->end_page_ptr = 0;
        }
        return ptr;
    }
    return NULL;  /* shouldn't ever get here */
}

使用者釋放chunk

void do_slabs_free(void *ptr, const size_t size) {
    unsigned char id = slabs_clsid(size);
    slabclass_t *p;
    p = &slabclass[id];
    //如果slot中已經裝滿了空閒chunk,那麼就得給slot分配一個更大的陣列,否則直接將空閒chunk放到slot中
    if (p->sl_curr == p->sl_total) { /* need more space on the free list */
        int new_size = (p->sl_total != 0) ? p->sl_total * 2 : 16;  /* 16 is arbitrary */
        void **new_slots = realloc(p->slots, new_size * sizeof(void *));
        if (new_slots == 0)
            return;
        p->slots = new_slots;
        p->sl_total = new_size;
    }
    p->slots[p->sl_curr++] = ptr;
    return;
}

相關推薦

Memcache原始碼閱讀4---記憶體管理

我看的原始碼版本是1.2.4 memcached的儲存 memcached擁有一個記憶體池,記憶體池中的記憶體分成多種大小的chunk,chunk的大小有一個基礎大小(最小的chunk大小,base chunk size),然後後面大小的是以settings

Memcache原始碼閱讀8---多執行緒

我看的原始碼版本是1.2.4 前面已經將memcached的主要內容討論完了。這裡來說說memcached怎麼使用多執行緒的吧。memcached的執行緒分為兩種,一個主執行緒,另外的是工作執行緒。一個主執行緒負責接收使用者的請求,接收到請求後將這個請求遞交給

Memcache原始碼閱讀1---看原始碼的心得

心得 我這是第一次看原始碼。說不上什麼心得,不過也總結一下~ 我覺得閱讀一個專案的原始碼,應該是先知道這個專案具體怎麼用之後,先估計一下作者的實現,然後再看原始碼來驗證自己的想法。 我這次閱讀原始碼是在沒有用過這個專案的前提下閱讀的,一開始的時候不知道從何

自動記憶體管理機制4- 記憶體分配和回收策略

自動記憶體管理機制(4)- 記憶體分配和回收策略 Java所承諾的自動記憶體管理主要是針對物件記憶體的回收和物件記憶體的分配。 在Java虛擬機器的五塊記憶體空間中,程式計數器、Java虛擬機器棧、本地方法棧記憶體的分配和回收都具有確定性,一般在編譯階段就能確定需要分配的記憶體大小,

Mybatis 原始碼分析9—— 事物管理

Mybatis 提供了事物的頂層介面: public interface Transaction { /** * Retrieve inner database connection * @return DataBase connection * @throw

Mybatis原始碼分析4—— Mapper的建立和獲取

Mybatis我們一般都是和Spring一起使用的,它們是怎麼融合到一起的,又各自發揮了什麼作用? 就拿這個Mapper來說,我們定義了一個介面,聲明瞭一個方法,然後對應的xml寫了這個sql語句, 它怎麼就執行成功了?這傢伙是怎麼實現的,帶著這個好奇心,我一步步跟蹤,慢慢揭開了它的

【筆記】ThreadPoolExecutor原始碼閱讀

執行緒數量的維護 執行緒池的大小有兩個重要的引數,一個是corePoolSize(核心執行緒池大小),另一個是maximumPoolSize(最大執行緒大小)。執行緒池主要根據這兩個引數對執行緒池中執行緒的數量進行維護。 需要注意的是,執行緒池建立之初是沒有任何可用執行緒的。只有在有任務到達後,才開始建立

以太坊原始碼解讀4Block類及其儲存

一、Block類 type Block struct { /******header*******/ header *Header /******header*******/ /******body*********/ uncle

## Zookeeper原始碼閱讀 Watcher

前言 好久沒有更新部落格了,最近這段時間過得很壓抑,終於開始踏上為換工作準備的正軌了,工作又真的很忙而且很瑣碎,讓自己有點煩惱,希望能早點結束這種狀態。 繼上次分析了ZK的ACL相關程式碼後,ZK裡非常重要的另一個特性就是Watcher機制了。其實在我看來,就ZK的使用而言,Watche機制是最核心的特性

【Java】「深入理解Java虛擬機器」學習筆記2-記憶體管理

 一、執行時資料區   JVM在執行Java程式的時候,將其執行時資料區劃分為若干不同區域。它們的用途和建立及銷燬的時間不同。      1、程式計數器(Program Counter Register)     是一塊很小的記憶體空間。當執行緒執行的是Java方法,它記錄的是當前正在執行的

程式設計師面試寶典隨筆記--記憶體管理詳解

  記憶體管理是C++最令人切齒痛恨的問題,也是C++最有爭議的問題,C++高手從中獲得了更好的效能,更大的自由,C++菜鳥的收穫則是一遍一遍的檢查程式碼和對C++的痛恨,但記憶體管理在C++中無處不在,記憶體洩漏幾乎在每個C++程式中都會發生,因此要想成為C++高手,記

Zookeeper原始碼閱讀 Server端Watcher

前言 前面一篇主要介紹了Watcher介面相關的介面和實體類,但是主要是zk客戶端相關的程式碼,如前一篇開頭所說,client需要把watcher註冊到server端,這一篇分析下server端的watcher。 主要分析Watchmanager類。 Watchmanager 這是WatchMan

Zookeeper原始碼閱讀 ACL基礎

前言 之前看程式碼的時候也同步看了看一些關於zk原始碼的部落格,有一兩篇講到了ZK裡ACL的基礎的結構,我自己這邊也看了看相關的程式碼,在這裡分享一下! ACL和ID ACL和ID都是有Jute生成的實體類,分別代表了ZK裡ACL和不同ACL模式下的具體實體。 ACL: public class A

Java原始碼系列4:String,StringBuilder,StringBuffer區別

hi,國慶節後第一篇。首先,祝大家國慶節快樂,然後祝大家上班快樂。 既然上班了,那就知識學起來,今天咱說一下String,StringBuffer和StringBuilder的區別,這是面試必問,但是如果是工作了的小哥哥和小姐姐,就不會傻白甜的問這個問題,但咱還是要知道的,畢竟要

Redis原始碼閱讀叢集-故障遷移(下)

Redis原始碼閱讀(六)叢集-故障遷移(下)   最近私人的事情比較多,沒有抽出時間來整理部落格。書接上文,上一篇裡總結了Redis故障遷移的幾個關鍵點,以及Redis中故障檢測的實現。本篇主要介紹叢集檢測到某主節點下線後,是如何選舉新的主節點的。注意到Redis叢集是無中心的,那麼使用分散式一

XSStrike原始碼閱讀2——四種模式

1.bruteforcer模式 功能介紹 根據使用者提供的payloads檔案去暴力測試每一個引數,以此來確定是否存在xss漏洞(說起來也就是一個兩層迴圈)。 具體實現 XSStrike3.0 bruteforcer.py原始碼如下: import copy from

Spark原始碼閱讀

強烈推薦 https://blog.csdn.net/weixin_41705780/article/details/79273666 總體架構 Spark工程下的模組 spark core, spark 核心 spark streaming, spark流計算(基

Redis底層詳解 記憶體管理

一、記憶體分配概述         redis 的記憶體分配,實質上是對 tcmalloc / jemalloc 的封裝。記憶體分配本質就是給定需要分配的大小,以位元組為單位,然後返回一個指向一段分配好的連續的記憶體空間的首指標。   &n

Koa原始碼閱讀從搭建Web伺服器說起

先複習一下使用原生 Node.js 搭建一個 Web 伺服器。 var http = require('http'); var server = http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'te

Koa原始碼閱讀上下文ctx

上篇提到,this.callback() 返回一個回撥函式,其實是以閉包的形式返回了一個區域性函式變數 handleRequest,供 Server 呼叫來處理 HTTP 請求。 callback() { const fn = compose(this.middleware); const han