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長度陣列)作用: 為了滿足需要變長度的結構體(結構體是可變長的)
- 陣列名不佔用空間,分配的記憶體是連續的
- 不會像定長陣列一樣浪費空間
- 不會像指標一樣需要分別分配記憶體,分別釋放記憶體
定長陣列使用方便, 但是卻浪費空間, 指標形式只多使用了一個指標的空間, 不會造成