1. 程式人生 > >swoole之memoryGlobal記憶體池分析

swoole之memoryGlobal記憶體池分析

記憶體池的作用:

直接使用系統呼叫malloc會有如下弊端:

  • 頻繁分配記憶體時會產生大量記憶體碎片
  • 頻繁分配記憶體增加系統呼叫開銷
  • 容易造成記憶體洩漏
記憶體池是預先申請一定數量的,大小相等的記憶體塊作為預備使用;當需要時向記憶體池分出一部分記憶體,若記憶體塊不夠使用時再向系統申請新的記憶體塊,下面就swoole的swMemoryGlobal記憶體池作為分析例子

swoole swMemoryPool 資料結構設計

swMemoryGlobal是swoole記憶體池實現一種方式,學習記憶體池主要是要掌握其資料結構的設計,memoryGlobal實現如下:

// src/memory/MemoryGlobal.c
typedef struct _swMemoryPool
{
    void *object;                                               // 指向swMemoryGlobal指標
    void* (*alloc)(struct _swMemoryPool *pool, uint32_t size);  // 分配記憶體函式指標
    void (*free)(struct _swMemoryPool *pool, void *ptr);        // 是否記憶體函式指標
    void (*destroy)(struct _swMemoryPool *pool);                // 銷燬記憶體函式指標
} swMemoryPool;

typedef struct _swMemoryGlobal
{
    uint8_t shared;                      
    uint32_t pagesize;                    // 指定每個swMemoryGlobal_page需要申請記憶體大小
    swLock lock;                          // 互斥鎖
    swMemoryGlobal_page *root_page;       // 指向第一個swMemoryGlobal_page指標,有頭指標可以銷燬記憶體池
    swMemoryGlobal_page *current_page;    // 指向當前swMemoryGlobal_page指標
    uint32_t current_offset;
} swMemoryGlobal;

typedef struct _swMemoryGlobal_page
{
    struct _swMemoryGlobal_page *next;    // 指向下一個節點
    char memory[0];                       // 這是一個柔性陣列,用於記錄申請記憶體後的記憶體地址
} swMemoryGlobal_page;

這三者之間的關係如下:

swMemoryPool

swMemoryPool可以看做是一個類,它提過了alloc,free,destory方法,以及object屬性,object實際上是指向swMemoryGlobal的指標,而alloc,free,destory
則是對object操作,即通過alloc,free,destory操作swMemoryGlobal上的內容,例如:

// src/core/base.c
//init global shared memory
SwooleG.memory_pool = swMemoryGlobal_new(SW_GLOBAL_MEMORY_PAGESIZE, 1);
SwooleGS = SwooleG.memory_pool->alloc(SwooleG.memory_pool, sizeof(SwooleGS_t));

以上程式碼是分配sizeof(SwooleGS_t)大小記憶體

swMemoryGlobal

swMemoryGlobal維護著一個連結串列,每個節點即swMemoryGlobal_page,root_page指向第一個節點,current_page指向當前節點,pagesize指為一個節點申請
記憶體大小,current_offset則表示一個節點已被使用記憶體

swMemoryGlobal_page

swoole根據swMemoryGlobal.pagesize申請指定大小的記憶體,如下:

// src/memory/MemoryGlobal.c
swMemoryGlobal_page *page = swMemoryGlobal_new_page(&gm);

上面說過swMemoryGlobal_page是一個連結串列節點,這裡需要說明的是第一個節點,第一個節點的current_offset為sizeof(swMemoryGlobal) + sizeof(swMemoryPool);
而並非為0;如下程式碼,當為第一個swMemoryGlobal_page申請記憶體後,立馬就為swMemoryPool和swMemoryGlobal分配記憶體

// src/memory/MemoryGlobal.c
gm.pagesize = pagesize;
// 系統申請一個pagesize大小記憶體
swMemoryGlobal_page *page = swMemoryGlobal_new_page(&gm);
if (page == NULL)
{
    return NULL;
}
if (swMutex_create(&gm.lock, shared) < 0)
{
    return NULL;
}
    
gm.root_page = page;

// page->memory為空閒記憶體
gm_ptr = (swMemoryGlobal *) page->memory;
gm.current_offset += sizeof(swMemoryGlobal);

// swMemoryPool指向空閒記憶體偏移地址,佔用sizeof(swMemoryPool)記憶體
swMemoryPool *allocator = (swMemoryPool *) (page->memory + gm.current_offset);
gm.current_offset += sizeof(swMemoryPool);

allocator->object = gm_ptr;
allocator->alloc = swMemoryGlobal_alloc;
allocator->destroy = swMemoryGlobal_destroy;
allocator->free = swMemoryGlobal_free;

// 將gm寫入到gm_ptr,即空閒記憶體前sizeof(gm)用於swMemoryGlobal
memcpy(gm_ptr, &gm, sizeof(gm));

分配記憶體

分配記憶體由swMemoryGlobal_alloc方法執行;該方法為swMemoryPool一個函式指標,如下

allocator->alloc = swMemoryGlobal_alloc;        // 分配方法
// src/core/base.c
//init global shared memory
SwooleG.memory_pool = swMemoryGlobal_new(SW_GLOBAL_MEMORY_PAGESIZE, 1);
SwooleGS = SwooleG.memory_pool->alloc(SwooleG.memory_pool, sizeof(SwooleGS_t));

// src/memory/MemoryGlobal.c
static void *swMemoryGlobal_alloc(swMemoryPool *pool, uint32_t size)
{
    swMemoryGlobal *gm = pool->object;
    gm->lock.lock(&gm->lock);
    if (size > gm->pagesize - sizeof(swMemoryGlobal_page)) // sizeof(swMemoryGlobal_page)為swMemoryGlobal_page型別的指標大小
    {
        swWarn("failed to alloc %d bytes, exceed the maximum size[%d].", size, gm->pagesize - (int) sizeof(swMemoryGlobal_page));
        gm->lock.unlock(&gm->lock);
        return NULL;
    }
    // 如果一個節點不夠分配記憶體,則重新申請一個新節點,並設定當前節點current_page為新節點
    if (gm->current_offset + size > gm->pagesize - sizeof(swMemoryGlobal_page))
    {
        swMemoryGlobal_page *page = swMemoryGlobal_new_page(gm);
        if (page == NULL)
        {
            swWarn("swMemoryGlobal_alloc alloc memory error.");
            gm->lock.unlock(&gm->lock);
            return NULL;
        }
        gm->current_page = page;
    }
    void *mem = gm->current_page->memory + gm->current_offset;
    gm->current_offset += size;
    gm->lock.unlock(&gm->lock);
    
    // 結果返回空閒記憶體的偏移地址
    return mem;
}

柔性陣列

柔性陣列(0長度陣列)作用: 為了滿足需要變長度的結構體(結構體是可變長的)

  • 陣列名不佔用空間,分配的記憶體是連續的
  • 不會像定長陣列一樣浪費空間
  • 不會像指標一樣需要分別分配記憶體,分別釋放記憶體

定長陣列使用方便, 但是卻浪費空間, 指標形式只多使用了一個指標的空間, 不會造成

我的筆記
柔性陣列參考

原文地址:https://segmentfault.com/a/1190000016679265