摘要:前言中為了更好的進行內存管理,減少頻繁分配釋放內存空間造成的損耗和內存碎片,程序設計并實現了三種不同功能的內存池,和。比較特殊的是單鏈表內存池的內存只能增加不會減少。
前言
Swoole 中為了更好的進行內存管理,減少頻繁分配釋放內存空間造成的損耗和內存碎片,程序設計并實現了三種不同功能的內存池:FixedPool,RingBuffer 和 MemoryGlobal。
其中 MemoryGlobal 用于全局變量 SwooleG.memory_pool,RingBuffer 用于 reactor 線程的緩沖區,FixedPool 用于 swoole_table 共享內存表。
swMemoryPool 內存池數據結構無論是哪種內存池,它的基礎數據結構都是 swMemoryPool:
typedef struct _swMemoryPool { void *object; void* (*alloc)(struct _swMemoryPool *pool, uint32_t size); void (*free)(struct _swMemoryPool *pool, void *ptr); void (*destroy)(struct _swMemoryPool *pool); } swMemoryPool;
可以看出來, swMemoryPool 更加類似于接口,規定了內存池需要定義的函數。
MemoryGlobal 內存池實現 MemoryGlobal 數據結構首先看一下 MemoryGlobal 的數據結構:
typedef struct _swMemoryGlobal_page { struct _swMemoryGlobal_page *next; char memory[0]; } swMemoryGlobal_page; typedef struct _swMemoryGlobal { uint8_t shared; uint32_t pagesize; swLock lock; swMemoryGlobal_page *root_page; swMemoryGlobal_page *current_page; uint32_t current_offset; } swMemoryGlobal;
可以很明顯的看出,MemoryGlobal 實際上就是一個單鏈表,root_page 是鏈表的頭,current_page 就是鏈表的尾,current_offset 指的是最后一個鏈表元素的偏移量。
比較特殊的是 MemoryGlobal 單鏈表內存池的內存只能增加不會減少。
MemoryGlobal 的創建#define SW_MIN_PAGE_SIZE 4096 swMemoryPool* swMemoryGlobal_new(uint32_t pagesize, uint8_t shared) { swMemoryGlobal gm, *gm_ptr; assert(pagesize >= SW_MIN_PAGE_SIZE); bzero(&gm, sizeof(swMemoryGlobal)); gm.shared = shared; gm.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; gm_ptr = (swMemoryGlobal *) page->memory; gm.current_offset += sizeof(swMemoryGlobal); 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; memcpy(gm_ptr, &gm, sizeof(gm)); return allocator; }
可以看到,每次申請創建 MemoryGlobal 內存不得小于 2k
創建的 MemoryGlobal 的 current_offset 被初始化為 swMemoryGlobal 與 swMemoryPool 的大小之和
返回的 allocator 類型是 swMemoryPool,其內存結構為:
swMemoryGlobal | swMemoryPool | memory |
---|
static swMemoryGlobal_page* swMemoryGlobal_new_page(swMemoryGlobal *gm) { swMemoryGlobal_page *page = (gm->shared == 1) ? sw_shm_malloc(gm->pagesize) : sw_malloc(gm->pagesize); if (page == NULL) { return NULL; } bzero(page, gm->pagesize); page->next = NULL; if (gm->current_page != NULL) { gm->current_page->next = page; } gm->current_page = page; gm->current_offset = 0; return page; }
鏈表元素的創建比較簡單,就是申請內存,初始化單鏈表的各個變量。
MemoryGlobal 內存的申請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)) { 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; } 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; }
申請內存之前需要先將互斥鎖加鎖以防多個線程或多個進程同時申請內存,導致數據混亂。
如果申請的內存大于單個鏈表元素的 pagesize,直接返回錯誤。
如果當前鏈表元素剩余的內存不足,那么就會重新申請一個新的鏈表元素
設置 current_offset,解鎖互斥鎖,返回內存地址。
MemoryGlobal 內存的釋放與銷毀static void swMemoryGlobal_free(swMemoryPool *pool, void *ptr) { swWarn("swMemoryGlobal Allocator don"t need to release."); } static void swMemoryGlobal_destroy(swMemoryPool *poll) { swMemoryGlobal *gm = poll->object; swMemoryGlobal_page *page = gm->root_page; swMemoryGlobal_page *next; do { next = page->next; sw_shm_free(page); page = next; } while (page); }
MemoryGlobal 不需要進行內存的釋放
MemoryGlobal 的銷毀就是循環單鏈表,然后釋放內存
RingBuffer 內存池實現 RingBuffer 的數據結構RingBuffer 類似于一個循環數組,每一次申請的一塊內存在該數組中占據一個位置,這些內存塊是可以不等長的,因此每個內存塊需要有一個記錄其長度的變量。
typedef struct { uint16_t lock; uint16_t index; uint32_t length; char data[0]; } swRingBuffer_item; typedef struct { uint8_t shared; uint8_t status; uint32_t size; uint32_t alloc_offset; uint32_t collect_offset; uint32_t alloc_count; sw_atomic_t free_count; void *memory; } swRingBuffer;
swRingBuffer 中非常重要的成員變量是 alloc_offset 與 collect_offset,alloc_offset 是當前循環數組中的起始地址,collect_offset 代表當前循環數組中可以被回收的內存地址。
free_count 是當前循環數組中可以被回收的個數。
status 為 0 代表循環數組當前占用的內存空間并沒有越過數組的結尾,也就是其地址是連續的,為 1 代表循環數組當前占用的內存空間一部分在循環數組的尾部,一部分在數組的頭部。
RingBuffer 的創建RingBuffer 的創建類似于 MemoryGlobal:
RingBuffer | swMemoryPool | memory |
---|
swMemoryPool *swRingBuffer_new(uint32_t size, uint8_t shared) { void *mem = (shared == 1) ? sw_shm_malloc(size) : sw_malloc(size); if (mem == NULL) { swWarn("malloc(%d) failed.", size); return NULL; } swRingBuffer *object = mem; mem += sizeof(swRingBuffer); bzero(object, sizeof(swRingBuffer)); object->size = (size - sizeof(swRingBuffer) - sizeof(swMemoryPool)); object->shared = shared; swMemoryPool *pool = mem; mem += sizeof(swMemoryPool); pool->object = object; pool->destroy = swRingBuffer_destory; pool->free = swRingBuffer_free; pool->alloc = swRingBuffer_alloc; object->memory = mem; swDebug("memory: ptr=%p", mem); return pool; }RingBuffer 內存的申請
若 free_count 大于 0,說明此時數組中有待回收的內存,需要進行內存回收
若當前占用的內存不是連續的,那么當前內存池剩余的容量就是 collect_offset - alloc_offset
若當前占用的內存是連續的,
而且數組當前 collect_offset 距離尾部的內存大于申請的內存數,那么剩余的容量就是 size - alloc_offset
數組當前內存位置距離尾部容量不足,那么就將當前內存到數組尾部打包成為一個 swRingBuffer_item 數組元素,并標志為待回收元素,設置 status 為 1,設置 alloc_offset 為數組首地址,此時剩余的容量就是 collect_offset 的地址
static void* swRingBuffer_alloc(swMemoryPool *pool, uint32_t size) { assert(size > 0); swRingBuffer *object = pool->object; swRingBuffer_item *item; uint32_t capacity; uint32_t alloc_size = size + sizeof(swRingBuffer_item); if (object->free_count > 0) { swRingBuffer_collect(object); } if (object->status == 0) { if (object->alloc_offset + alloc_size >= (object->size - sizeof(swRingBuffer_item))) { uint32_t skip_n = object->size - object->alloc_offset; if (skip_n >= sizeof(swRingBuffer_item)) { item = object->memory + object->alloc_offset; item->lock = 0; item->length = skip_n - sizeof(swRingBuffer_item); sw_atomic_t *free_count = &object->free_count; sw_atomic_fetch_add(free_count, 1); } object->alloc_offset = 0; object->status = 1; capacity = object->collect_offset - object->alloc_offset; } else { capacity = object->size - object->alloc_offset; } } else { capacity = object->collect_offset - object->alloc_offset; } if (capacity < alloc_size) { return NULL; } item = object->memory + object->alloc_offset; item->lock = 1; item->length = size; item->index = object->alloc_count; object->alloc_offset += alloc_size; object->alloc_count ++; swDebug("alloc: ptr=%p", (void * )((void * )item->data - object->memory)); return item->data; }RingBuffer 內存的回收
當 RingBuffer 的 free_count 大于 0 的時候,就說明當前內存池存在需要回收的元素,每次在申請新的內存時,都會調用這個函數來回收內存。
回收內存時,本函數只會回收連續的多個空余的內存元素,若多個待回收的內存元素之間相互隔離,那么這些內存元素不會被回收。
static void swRingBuffer_collect(swRingBuffer *object) { swRingBuffer_item *item; sw_atomic_t *free_count = &object->free_count; int count = object->free_count; int i; uint32_t n_size; for (i = 0; i < count; i++) { item = object->memory + object->collect_offset; if (item->lock == 0) { n_size = item->length + sizeof(swRingBuffer_item); object->collect_offset += n_size; if (object->collect_offset + sizeof(swRingBuffer_item) >object->size || object->collect_offset >= object->size) { object->collect_offset = 0; object->status = 0; } sw_atomic_fetch_sub(free_count, 1); } else { break; } } }RingBuffer 內存的釋放
內存的釋放很簡單,只需要設置 lock 為 0,并且增加 free_count 的數量即可:
static void swRingBuffer_free(swMemoryPool *pool, void *ptr) { swRingBuffer *object = pool->object; swRingBuffer_item *item = ptr - sizeof(swRingBuffer_item); assert(ptr >= object->memory); assert(ptr <= object->memory + object->size); assert(item->lock == 1); if (item->lock != 1) { swDebug("invalid free: index=%d, ptr=%p", item->index, (void * )((void * )item->data - object->memory)); } else { item->lock = 0; } swDebug("free: ptr=%p", (void * )((void * )item->data - object->memory)); sw_atomic_t *free_count = &object->free_count; sw_atomic_fetch_add(free_count, 1); }RingBuffer 內存的銷毀
static void swRingBuffer_destory(swMemoryPool *pool) { swRingBuffer *object = pool->object; if (object->shared) { sw_shm_free(object); } else { sw_free(object); } }
值得注意的是,RingBuffer 除了原子鎖之外就沒有任何鎖了,在申請與釋放過程的代碼中也沒有看出來是線程安全的無鎖數據結構,個人認為 RingBuffer 并非是線程安全/進程安全的數據結構,因此利用這個內存池申請共享內存時,需要自己進行加鎖。
FixedPool 內存池實現 FixedPool 數據結構FixedPool 是隨機分配內存池,將一整塊內存空間切分成等大小的一個個小塊,每次分配其中的一個小塊作為要使用的內存,這些小塊以雙向鏈表的形式存儲。
typedef struct _swFixedPool_slice { uint8_t lock; struct _swFixedPool_slice *next; struct _swFixedPool_slice *pre; char data[0]; } swFixedPool_slice; typedef struct _swFixedPool { void *memory; size_t size; swFixedPool_slice *head; swFixedPool_slice *tail; /** * total memory size */ uint32_t slice_num; /** * memory usage */ uint32_t slice_use; /** * Fixed slice size, not include the memory used by swFixedPool_slice */ uint32_t slice_size; /** * use shared memory */ uint8_t shared; } swFixedPool;FixedPool 內存池的創建
FixedPool 內存池的創建有兩個函數 swFixedPool_new 與 swFixedPool_new2,其中 swFixedPool_new2 是利用已有的內存基礎上來構建內存池,這個也是 table 共享內存表創建的方法。
swMemoryPool* swFixedPool_new2(uint32_t slice_size, void *memory, size_t size) { swFixedPool *object = memory; memory += sizeof(swFixedPool); bzero(object, sizeof(swFixedPool)); object->slice_size = slice_size; object->size = size - sizeof(swMemoryPool) - sizeof(swFixedPool); object->slice_num = object->size / (slice_size + sizeof(swFixedPool_slice)); swMemoryPool *pool = memory; memory += sizeof(swMemoryPool); bzero(pool, sizeof(swMemoryPool)); pool->object = object; pool->alloc = swFixedPool_alloc; pool->free = swFixedPool_free; pool->destroy = swFixedPool_destroy; object->memory = memory; /** * init linked list */ swFixedPool_init(object); return pool; }
內存池的創建和前兩個大同小異,只是這次多了 swFixedPool_init 這個構建雙向鏈表的過程:
static void swFixedPool_init(swFixedPool *object) { swFixedPool_slice *slice; void *cur = object->memory; void *max = object->memory + object->size; do { slice = (swFixedPool_slice *) cur; bzero(slice, sizeof(swFixedPool_slice)); if (object->head != NULL) { object->head->pre = slice; slice->next = object->head; } else { object->tail = slice; } object->head = slice; cur += (sizeof(swFixedPool_slice) + object->slice_size); if (cur < max) { slice->pre = (swFixedPool_slice *) cur; } else { slice->pre = NULL; break; } } while (1); }
可以看出來,程序從內存空間的首部開始,每次初始化一個 slice 大小的空間,并插入到鏈表的頭部,因此整個鏈表的內存地址和 memory 的地址是相反的。
FixedPool 內存池的申請static void* swFixedPool_alloc(swMemoryPool *pool, uint32_t size) { swFixedPool *object = pool->object; swFixedPool_slice *slice; slice = object->head; if (slice->lock == 0) { slice->lock = 1; object->slice_use ++; /** * move next slice to head (idle list) */ object->head = slice->next; slice->next->pre = NULL; /* * move this slice to tail (busy list) */ object->tail->next = slice; slice->next = NULL; slice->pre = object->tail; object->tail = slice; return slice->data; } else { return NULL; } }
首先獲取內存池鏈表首部的節點,并判斷該節點是否被占用,如果被占用,說明內存池已滿,返回null(因為所有被占用的節點都會被放到尾部);如果未被占用,則將該節點的下一個節點移到首部,并將該節點移動到尾部,標記該節點為占用狀態,返回該節點的數據域。
FixedPool 內存池的釋放static void swFixedPool_free(swMemoryPool *pool, void *ptr) { swFixedPool *object = pool->object; swFixedPool_slice *slice; assert(ptr > object->memory && ptr < object->memory + object->size); slice = ptr - sizeof(swFixedPool_slice); if (slice->lock) { object->slice_use--; } slice->lock = 0; //list head, AB if (slice->pre == NULL) { return; } //list tail, DE if (slice->next == NULL) { slice->pre->next = NULL; object->tail = slice->pre; } //middle BCD else { slice->pre->next = slice->next; slice->next->pre = slice->pre; } slice->pre = NULL; slice->next = object->head; object->head->pre = slice; object->head = slice; }
首先通過移動 ptr 指針獲得 slice 對象,并將占用標記 lock 置為 0。如果該節點為頭節點,則直接返回。如果不是頭節點,則將該節點移動到鏈表頭部。
FixedPool 內存池的銷毀static void swFixedPool_destroy(swMemoryPool *pool) { swFixedPool *object = pool->object; if (object->shared) { sw_shm_free(object); } else { sw_free(object); } }
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/29222.html
摘要:如果互斥鎖的持有者死亡了,或者持有這樣的互斥鎖的進程了互斥鎖所在的共享內存或者持有這樣的互斥鎖的進程執行了調用,則會解除鎖定該互斥鎖。互斥鎖的下一個持有者將獲取該互斥鎖并返回錯誤。 前言 swoole_table 一個基于共享內存和鎖實現的超高性能,并發數據結構。用于解決多進程/多線程數據共享和同步加鎖問題。 swoole_table 的數據結構 swoole_table 實際上...
摘要:內存池的作用直接使用系統調用會有如下弊端頻繁分配內存時會產生大量內存碎片頻繁分配內存增加系統調用開銷容易造成內存泄漏內存池是預先申請一定數量的,大小相等的內存塊作為預備使用當需要時向內存池分出一部分內存,若內存塊不夠使用時再向系統申請新的內 內存池的作用: 直接使用系統調用malloc會有如下弊端: 頻繁分配內存時會產生大量內存碎片 頻繁分配內存增加系統調用開銷 容易造成內存泄漏 ...
摘要:前言我們知道,由于沒有多線程模型,所以更多的使用多進程模型,因此代碼相對來說更加簡潔,減少了各種線程鎖的阻塞與同步,但是也帶來了新的問題數據同步。相比多線程之前可以直接共享進程的內存,進程之間數據的相互同步依賴于共享內存。 前言 我們知道,由于 PHP 沒有多線程模型,所以 swoole 更多的使用多進程模型,因此代碼相對來說更加簡潔,減少了各種線程鎖的阻塞與同步,但是也帶來了新的問題...
摘要:的數據結構數據結構中是鏈表元素的個數,是緩沖區創建時,鏈表元素約定的大小實際大小不一定是這個值,是實際上緩沖區占用的內存總大小。中的有三種,分別應用于緩存數據發送文件提醒連接關閉三種情景。指的是元素的內存大小。 前言 swoole 中數據的接受與發送(例如 reactor 線程接受客戶端消息、發送給客戶端的消息、接受到的來自 worker 的消息、要發送給 worker 的消息等等)都...
摘要:前言內存數據結構,類似于的通道,底層基于共享內存互斥鎖實現,可實現用戶態的高性能內存隊列。是當前隊列占用的內存大小,用來指定是否使用共享內存是否使用鎖是否使用通知。 前言 內存數據結構 Channel,類似于 Go 的 chan 通道,底層基于 共享內存 + Mutex 互斥鎖實現,可實現用戶態的高性能內存隊列。Channel 可用于多進程環境下,底層在讀取寫入時會自動加鎖,應用層不需...
閱讀 3179·2023-04-25 17:19
閱讀 625·2021-11-23 09:51
閱讀 1352·2021-11-08 13:19
閱讀 787·2021-09-29 09:34
閱讀 1686·2021-09-28 09:36
閱讀 1502·2021-09-22 14:59
閱讀 2718·2019-08-29 16:38
閱讀 2062·2019-08-26 13:40