preface
Swoole
In order to better manage the memory and reduce the loss and memory fragmentation caused by frequent allocation and release of memory space, three memory pools with different functions are designed and implementedFixedPool
,RingBuffer
andMemoryGlobal
。
amongMemoryGlobal
For global variablesSwooleG.memory_pool
,RingBuffer
be used forreactor
The buffer of the thread,FixedPool
be used forswoole_table
Shared memory table.
swMemoryPool
Memory pool data structure
No matter what kind of memory pool, its basic data structure isswMemoryPool
:
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;
You can see that,swMemoryPool
More similar to the interface, it specifies the functions that the memory pool needs to define.
MemoryGlobal
Memory pool implementation
MemoryGlobal
data structure
Let’s look at it firstMemoryGlobal
Data structure of the system:
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;
It’s obvious that,MemoryGlobal
It’s actually a single linked list,root_page
It’s the head of the list,current_page
It’s the end of the list,current_offset
Refers to the offset of the last linked list element.
What’s special is thatMemoryGlobal
The memory of single linked list memory pool can only be increased but not decreased.
MemoryGlobal
Creation of
#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;
}
- As you can see, each application is created
MemoryGlobal
Memory must not be less than2k
- Created
MemoryGlobal
Ofcurrent_offset
Is initialized toswMemoryGlobal
AndswMemoryPool
The sum of the sizes of -
Returned
allocator
The type isswMemoryPool
Its memory structure is as follows: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;
}
The creation of linked list elements is relatively simple, which is to apply for memory and initialize the variables of single linked list.
MemoryGlobal
Memory application
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;
}
- Before applying for memory, mutex should be locked to prevent multiple threads or processes from applying for memory at the same time, resulting in data confusion.
- If the requested memory is larger than the memory of a single linked list element
pagesize
, return the error directly. - If the remaining memory of the current linked list element is insufficient, a new linked list element will be applied for
- set up
current_offset
To unlock the mutex and return the memory address.
MemoryGlobal
Release and destroy of memory
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
There is no need to release memory -
MemoryGlobal
The destruction of single linked list is to cycle single linked list, and then release memory
RingBuffer
Memory pool implementation
RingBuffer
Data structure of
RingBuffer
Similar to a circular array, each memory block applied occupies a position in the array. These memory blocks can be unequal in length, so each memory block needs a variable to record its length.
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
The most important member variables in arealloc_offset
Andcollect_offset
,alloc_offset
Is the starting address in the current loop array,collect_offset
Represents the memory address in the current loop array that can be reclaimed. -
free_count
Is the number that can be recycled in the current loop array. -
status
A value of 0 means that the memory space currently occupied by the circular array does not cross the end of the array, that is, its address is continuous. A value of 1 means that the memory space currently occupied by the circular array is partly at the end of the circular array and partly at the head of the array.
RingBuffer
Creation of
RingBuffer
The creation of is similar toMemoryGlobal
:
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
Memory application
- if
free_count
Greater than 0 indicates that the memory to be reclaimed in the array needs to be reclaimed - If the memory currently occupied is not continuous, the remaining capacity of the current memory pool is
collect_offset - alloc_offset
-
If the memory currently occupied is continuous,
- And the array is currently
collect_offset
If the memory at the end of the distance is greater than the number of memory requested, then the remaining capacity issize - alloc_offset
- If the distance between the current memory location and the tail of the array is insufficient, then the current memory location and the tail of the array will be packed into one
swRingBuffer_item
Array elements, and marked as the elements to be recycledstatus
Set to 1alloc_offset
For the first address of the array, the remaining capacity iscollect_offset
Your address
- And the array is currently
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
Memory recovery
- When
RingBuffer
Offree_count
When it is greater than 0, it means that there are elements in the current memory pool that need to be recycled. This function will be called to recycle memory every time when applying for new memory. - When reclaiming memory, this function will only reclaim consecutive multiple spare memory elements. If multiple memory elements to be reclaimed are isolated from each other, these memory elements will not be reclaimed.
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
Memory release
Memory release is very simple, just need to setlock
0, and increasefree_count
The number of samples is as follows:
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
Destruction of memory
static void swRingBuffer_destory(swMemoryPool *pool)
{
swRingBuffer *object = pool->object;
if (object->shared)
{
sw_shm_free(object);
}
else
{
sw_free(object);
}
}
- It’s worth noting that,
RingBuffer
In addition to the atomic lock, there is no lock. In the code of the application and release process, there is no thread safe lock free data structure, I thinkRingBuffer
It is not a thread safe / process safe data structure, so when using this memory pool to apply for shared memory, you need to lock it yourself.
FixedPool
Memory pool implementation
FixedPool
data structure
FixedPool
Is a random allocation of memory pool, a block of memory space will be divided into small pieces of equal size, each time one of the small pieces is allocated as the memory to be used, these small pieces are stored in the form of two-way linked list.
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
Creation of memory pool
FixedPool
There are two functions to create a memory poolswFixedPool_new
AndswFixedPool_new2
Of whichswFixedPool_new2
It is to build a memory pool based on the existing memorytable
How to create a shared memory 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;
}
The creation of memory pool is similar to the first two, but this time there are moreswFixedPool_init
This is the process of building a two-way linked list
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);
}
As you can see, the program starts from the beginning of the memory space, initializing one at a timeslice
The size of the space and insert it into the head of the linked list, so the memory address and address of the whole linked listmemory
The address of is the opposite.
FixedPool
Application of memory pool
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;
}
}
- First, get the node at the head of the memory pool chain table, and judge whether the node is occupied. If it is occupied, the memory pool is full, and return null (because all occupied nodes will be put to the tail). If it is not occupied, move the next node to the head, and move the node to the tail, mark the node as occupied, and return the number of the node According to the domain.
FixedPool
Release of memory pool
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;
}
- First, by moving
ptr
Pointer acquisitionslice
Object, and will occupy the taglock
Set to 0. If the node is the head node, it is returned directly. If it is not the head node, the node is moved to the head of the linked list.
FixedPool
Destruction of memory pool
static void swFixedPool_destroy(swMemoryPool *pool)
{
swFixedPool *object = pool->object;
if (object->shared)
{
sw_shm_free(object);
}
else
{
sw_free(object);
}
}