swoole之memoryGlobal內存池分析

內存池的做用:

直接使用系統調用malloc會有以下弊端:php

  • 頻繁分配內存時會產生大量內存碎片
  • 頻繁分配內存增長系統調用開銷
  • 容易形成內存泄漏
內存池是預先申請必定數量的,大小相等的內存塊做爲預備使用;當須要時向內存池分出一部份內存,若內存塊不夠使用時再向系統申請新的內存塊,下面就swoole的swMemoryGlobal內存池做爲分析例子

swoole swMemoryPool 數據結構設計

swMemoryGlobal是swoole內存池實現一種方式,學習內存池主要是要掌握其數據結構的設計,memoryGlobal實現以下:html

// 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;

這三者之間的關係以下:
圖片描述git

swMemoryPool

swMemoryPool能夠看作是一個類,它提過了alloc,free,destory方法,以及object屬性,object其實是指向swMemoryGlobal的指針,而alloc,free,destory
則是對object操做,即經過alloc,free,destory操做swMemoryGlobal上的內容,例如:github

// 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則表示一個節點已被使用內存swoole

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長度數組)做用: 爲了知足須要變長度的結構體(結構體是可變長的)ui

  • 數組名不佔用空間,分配的內存是連續的
  • 不會像定長數組同樣浪費空間
  • 不會像指針同樣須要分別分配內存,分別釋放內存

定長數組使用方便, 可是卻浪費空間, 指針形式只多使用了一個指針的空間, 不會形成

個人筆記
柔性數組參考

相關文章
相關標籤/搜索