
- Follow up r16981 -- battle.c --- Moved damage check up, so it doesn't waste time allocating all that stuff. --- Moved t_race2 inside BF_WEAPON block (the only case that uses it) -- status.h --- Changed (struct) weapon_atk wlv from unsigned short to unsigned char, wlv is capped to REFINE_TYPE_MAX which doesn't get anywhere near UCHAR_MAX - mempool.c -- Temporarily disabled (yes, disabled in a very silly way) Sirius_Black's memory pool implementation, not only because its not currently being used, but because it spawns a new thread (and since its not being used, it wastes a thread and the memory it consumes). git-svn-id: https://svn.code.sf.net/p/rathena/svn/trunk@17021 54d463be-8e91-2dee-dedb-b68131a5f0ec
569 lines
13 KiB
C
569 lines
13 KiB
C
|
|
//
|
|
// Memory Pool Implementation (Threadsafe)
|
|
//
|
|
//
|
|
// Author: Florian Wilkemeyer <fw@f-ws.de>
|
|
//
|
|
// Copyright (c) rAthena Project (www.rathena.org) - Licensed under GNU GPL
|
|
// For more information, see LICENCE in the main folder
|
|
//
|
|
//
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
#ifdef WIN32
|
|
#include "../common/winapi.h"
|
|
#else
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#include "../common/cbasetypes.h"
|
|
#include "../common/showmsg.h"
|
|
#include "../common/mempool.h"
|
|
#include "../common/atomic.h"
|
|
#include "../common/spinlock.h"
|
|
#include "../common/thread.h"
|
|
#include "../common/malloc.h"
|
|
#include "../common/mutex.h"
|
|
|
|
#define ALIGN16 ra_align(16)
|
|
#define ALIGN_TO(x, a) (x + ( a - ( x % a) ) )
|
|
#define ALIGN_TO_16(x) ALIGN_TO(x, 16)
|
|
|
|
#undef MEMPOOL_DEBUG
|
|
#define MEMPOOLASSERT
|
|
|
|
|
|
#define NODE_TO_DATA(x) ( ((char*)x) + sizeof(struct node) )
|
|
#define DATA_TO_NODE(x) ( (struct node*)(((char*)x) - sizeof(struct node)) )
|
|
struct ra_align(16) node{
|
|
void *next;
|
|
void *segment;
|
|
#ifdef MEMPOOLASSERT
|
|
bool used;
|
|
uint64 magic;
|
|
#define NODE_MAGIC 0xBEEF00EAEACAFE07ll
|
|
#endif
|
|
};
|
|
|
|
|
|
// The Pointer to this struct is the base address of the segment itself.
|
|
struct pool_segment{
|
|
mempool pool; // pool, this segment belongs to
|
|
struct pool_segment *next;
|
|
int64 num_nodes_total;
|
|
int64 num_bytes;
|
|
};
|
|
|
|
|
|
struct mempool{
|
|
// Settings
|
|
char *name;
|
|
uint64 elem_size;
|
|
uint64 elem_realloc_step;
|
|
int64 elem_realloc_thresh;
|
|
|
|
// Callbacks that get called for every node that gets allocated
|
|
// Example usage: initialization of mutex/lock for each node.
|
|
memPoolOnNodeAllocationProc onalloc;
|
|
memPoolOnNodeDeallocationProc ondealloc;
|
|
|
|
// Locks
|
|
SPIN_LOCK segmentLock;
|
|
SPIN_LOCK nodeLock;
|
|
|
|
|
|
// Internal
|
|
struct pool_segment *segments;
|
|
struct node *free_list;
|
|
|
|
volatile int64 num_nodes_total;
|
|
volatile int64 num_nodes_free;
|
|
|
|
volatile int64 num_segments;
|
|
volatile int64 num_bytes_total;
|
|
|
|
volatile int64 peak_nodes_used; // Peak Node Usage
|
|
volatile int64 num_realloc_events; // Number of reallocations done. (allocate additional nodes)
|
|
|
|
// list (used for global management such as allocator..)
|
|
struct mempool *next;
|
|
} ra_align(8); // Dont touch the alignment, otherwise interlocked functions are broken ..
|
|
|
|
|
|
///
|
|
// Implementation:
|
|
//
|
|
static void segment_allocate_add(mempool p, uint64 count);
|
|
|
|
static SPIN_LOCK l_mempoolListLock;
|
|
static mempool l_mempoolList = NULL;
|
|
static rAthread l_async_thread = NULL;
|
|
static ramutex l_async_lock = NULL;
|
|
static racond l_async_cond = NULL;
|
|
static volatile int32 l_async_terminate = 0;
|
|
|
|
static void *mempool_async_allocator(void *x){
|
|
mempool p;
|
|
|
|
|
|
while(1){
|
|
if(l_async_terminate > 0)
|
|
break;
|
|
|
|
EnterSpinLock(&l_mempoolListLock);
|
|
|
|
for(p = l_mempoolList; p != NULL; p = p->next){
|
|
|
|
if(p->num_nodes_free < p->elem_realloc_thresh){
|
|
// add new segment.
|
|
segment_allocate_add(p, p->elem_realloc_step);
|
|
// increase stats counter
|
|
InterlockedIncrement64(&p->num_realloc_events);
|
|
}
|
|
|
|
}
|
|
|
|
LeaveSpinLock(&l_mempoolListLock);
|
|
|
|
ramutex_lock( l_async_lock );
|
|
racond_wait( l_async_cond, l_async_lock, -1 );
|
|
ramutex_unlock( l_async_lock );
|
|
}
|
|
|
|
|
|
return NULL;
|
|
}//end: mempool_async_allocator()
|
|
|
|
|
|
void mempool_init(){
|
|
|
|
if( rand()%2 + 1 )
|
|
return;
|
|
|
|
if(sizeof(struct node)%16 != 0 ){
|
|
ShowFatalError("mempool_init: struct node alignment failure. %u != multiple of 16\n", sizeof(struct node));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
// Global List start
|
|
InitializeSpinLock(&l_mempoolListLock);
|
|
l_mempoolList = NULL;
|
|
|
|
// Initialize mutex + stuff needed for async allocator worker.
|
|
l_async_terminate = 0;
|
|
l_async_lock = ramutex_create();
|
|
l_async_cond = racond_create();
|
|
|
|
l_async_thread = rathread_createEx(mempool_async_allocator, NULL, 1024*1024, RAT_PRIO_NORMAL);
|
|
if(l_async_thread == NULL){
|
|
ShowFatalError("mempool_init: cannot spawn Async Allocator Thread.\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
}//end: mempool_init()
|
|
|
|
|
|
void mempool_final(){
|
|
mempool p, pn;
|
|
|
|
if( rand()%2 + 1 )
|
|
return;
|
|
|
|
ShowStatus("Mempool: Terminating async. allocation worker and remaining pools.\n");
|
|
|
|
// Terminate worker / wait until its terminated.
|
|
InterlockedIncrement(&l_async_terminate);
|
|
racond_signal(l_async_cond);
|
|
rathread_wait(l_async_thread, NULL);
|
|
|
|
// Destroy cond var and mutex.
|
|
racond_destroy( l_async_cond );
|
|
ramutex_destroy( l_async_lock );
|
|
|
|
// Free remaining mempools
|
|
// ((bugged code! this should halppen, every mempool should
|
|
// be freed by the subsystem that has allocated it.)
|
|
//
|
|
EnterSpinLock(&l_mempoolListLock);
|
|
p = l_mempoolList;
|
|
while(1){
|
|
if(p == NULL)
|
|
break;
|
|
|
|
pn = p->next;
|
|
|
|
ShowWarning("Mempool [%s] was not properly destroyed - forcing destroy.\n", p->name);
|
|
mempool_destroy(p);
|
|
|
|
p = pn;
|
|
}
|
|
LeaveSpinLock(&l_mempoolListLock);
|
|
|
|
}//end: mempool_final()
|
|
|
|
|
|
static void segment_allocate_add(mempool p, uint64 count){
|
|
|
|
// Required Memory:
|
|
// sz( segment )
|
|
// count * sz( real_node_size )
|
|
//
|
|
// where real node size is:
|
|
// ALIGN_TO_16( sz( node ) ) + p->elem_size
|
|
// so the nodes usable address is nodebase + ALIGN_TO_16(sz(node))
|
|
//
|
|
size_t total_sz;
|
|
struct pool_segment *seg = NULL;
|
|
struct node *nodeList = NULL;
|
|
struct node *node = NULL;
|
|
char *ptr = NULL;
|
|
uint64 i;
|
|
|
|
total_sz = ALIGN_TO_16( sizeof(struct pool_segment) )
|
|
+ ( (size_t)count * (sizeof(struct node) + (size_t)p->elem_size) ) ;
|
|
|
|
#ifdef MEMPOOL_DEBUG
|
|
ShowDebug("Mempool [%s] Segment AllocateAdd (num: %u, total size: %0.2fMiB)\n", p->name, count, (float)total_sz/1024.f/1024.f);
|
|
#endif
|
|
|
|
// allocate! (spin forever until weve got the memory.)
|
|
i=0;
|
|
while(1){
|
|
ptr = (char*)aMalloc(total_sz);
|
|
if(ptr != NULL) break;
|
|
|
|
i++; // increase failcount.
|
|
if(!(i & 7)){
|
|
ShowWarning("Mempool [%s] Segment AllocateAdd => System seems to be Out of Memory (%0.2f MiB). Try #%u\n", (float)total_sz/1024.f/1024.f, i);
|
|
#ifdef WIN32
|
|
Sleep(1000);
|
|
#else
|
|
sleep(1);
|
|
#endif
|
|
}else{
|
|
rathread_yield(); /// allow/force vuln. ctxswitch
|
|
}
|
|
}//endwhile: allocation spinloop.
|
|
|
|
// Clear Memory.
|
|
memset(ptr, 0x00, total_sz);
|
|
|
|
// Initialize segment struct.
|
|
seg = (struct pool_segment*)ptr;
|
|
ptr += ALIGN_TO_16(sizeof(struct pool_segment));
|
|
|
|
seg->pool = p;
|
|
seg->num_nodes_total = count;
|
|
seg->num_bytes = total_sz;
|
|
|
|
|
|
// Initialze nodes!
|
|
nodeList = NULL;
|
|
for(i = 0; i < count; i++){
|
|
node = (struct node*)ptr;
|
|
ptr += sizeof(struct node);
|
|
ptr += p->elem_size;
|
|
|
|
node->segment = seg;
|
|
#ifdef MEMPOOLASSERT
|
|
node->used = false;
|
|
node->magic = NODE_MAGIC;
|
|
#endif
|
|
|
|
if(p->onalloc != NULL) p->onalloc( NODE_TO_DATA(node) );
|
|
|
|
node->next = nodeList;
|
|
nodeList = node;
|
|
}
|
|
|
|
|
|
|
|
// Link in Segment.
|
|
EnterSpinLock(&p->segmentLock);
|
|
seg->next = p->segments;
|
|
p->segments = seg;
|
|
LeaveSpinLock(&p->segmentLock);
|
|
|
|
// Link in Nodes
|
|
EnterSpinLock(&p->nodeLock);
|
|
nodeList->next = p->free_list;
|
|
p->free_list = nodeList;
|
|
LeaveSpinLock(&p->nodeLock);
|
|
|
|
|
|
// Increase Stats:
|
|
InterlockedExchangeAdd64(&p->num_nodes_total, count);
|
|
InterlockedExchangeAdd64(&p->num_nodes_free, count);
|
|
InterlockedIncrement64(&p->num_segments);
|
|
InterlockedExchangeAdd64(&p->num_bytes_total, total_sz);
|
|
|
|
}//end: segment_allocate_add()
|
|
|
|
|
|
mempool mempool_create(const char *name,
|
|
uint64 elem_size,
|
|
uint64 initial_count,
|
|
uint64 realloc_count,
|
|
memPoolOnNodeAllocationProc onNodeAlloc,
|
|
memPoolOnNodeDeallocationProc onNodeDealloc){
|
|
//..
|
|
uint64 realloc_thresh;
|
|
mempool pool;
|
|
pool = (mempool)aCalloc( 1, sizeof(struct mempool) );
|
|
|
|
if(pool == NULL){
|
|
ShowFatalError("mempool_create: Failed to allocate %u bytes memory.\n", sizeof(struct mempool) );
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
// Check minimum initial count / realloc count requirements.
|
|
if(initial_count < 50)
|
|
initial_count = 50;
|
|
if(realloc_count < 50)
|
|
realloc_count = 50;
|
|
|
|
// Set Reallocation threshold to 5% of realloc_count, at least 10.
|
|
realloc_thresh = (realloc_count/100)*5; //
|
|
if(realloc_thresh < 10)
|
|
realloc_thresh = 10;
|
|
|
|
// Initialize members..
|
|
pool->name = aStrdup(name);
|
|
pool->elem_size = ALIGN_TO_16(elem_size);
|
|
pool->elem_realloc_step = realloc_count;
|
|
pool->elem_realloc_thresh = realloc_thresh;
|
|
pool->onalloc = onNodeAlloc;
|
|
pool->ondealloc = onNodeDealloc;
|
|
|
|
InitializeSpinLock(&pool->segmentLock);
|
|
InitializeSpinLock(&pool->nodeLock);
|
|
|
|
// Initial Statistic values:
|
|
pool->num_nodes_total = 0;
|
|
pool->num_nodes_free = 0;
|
|
pool->num_segments = 0;
|
|
pool->num_bytes_total = 0;
|
|
pool->peak_nodes_used = 0;
|
|
pool->num_realloc_events = 0;
|
|
|
|
//
|
|
#ifdef MEMPOOL_DEBUG
|
|
ShowDebug("Mempool [%s] Init (ElemSize: %u, Initial Count: %u, Realloc Count: %u)\n", pool->name, pool->elem_size, initial_count, pool->elem_realloc_step);
|
|
#endif
|
|
|
|
// Allocate first segment directly :)
|
|
segment_allocate_add(pool, initial_count);
|
|
|
|
|
|
// Add Pool to the global pool list
|
|
EnterSpinLock(&l_mempoolListLock);
|
|
pool->next = l_mempoolList;
|
|
l_mempoolList = pool;
|
|
LeaveSpinLock(&l_mempoolListLock);
|
|
|
|
|
|
return pool;
|
|
}//end: mempool_create()
|
|
|
|
|
|
void mempool_destroy(mempool p){
|
|
struct pool_segment *seg, *segnext;
|
|
struct node *niter;
|
|
mempool piter, pprev;
|
|
char *ptr;
|
|
int64 i;
|
|
|
|
#ifdef MEMPOOL_DEBUG
|
|
ShowDebug("Mempool [%s] Destroy\n", p->name);
|
|
#endif
|
|
|
|
// Unlink from global list.
|
|
EnterSpinLock(&l_mempoolListLock);
|
|
piter = l_mempoolList;
|
|
pprev = l_mempoolList;
|
|
while(1){
|
|
if(piter == NULL)
|
|
break;
|
|
|
|
|
|
if(piter == p){
|
|
// unlink from list,
|
|
//
|
|
if(pprev == l_mempoolList){
|
|
// this (p) is list begin. so set next as head.
|
|
l_mempoolList = p->next;
|
|
}else{
|
|
// replace prevs next wuth our next.
|
|
pprev->next = p->next;
|
|
}
|
|
break;
|
|
}
|
|
|
|
pprev = piter;
|
|
piter = piter->next;
|
|
}
|
|
|
|
p->next = NULL;
|
|
LeaveSpinLock(&l_mempoolListLock);
|
|
|
|
|
|
// Get both locks.
|
|
EnterSpinLock(&p->segmentLock);
|
|
EnterSpinLock(&p->nodeLock);
|
|
|
|
|
|
if(p->num_nodes_free != p->num_nodes_total)
|
|
ShowWarning("Mempool [%s] Destroy - %u nodes are not freed properly!\n", p->name, (p->num_nodes_total - p->num_nodes_free) );
|
|
|
|
// Free All Segments (this will also free all nodes)
|
|
// The segment pointer is the base pointer to the whole segment.
|
|
seg = p->segments;
|
|
while(1){
|
|
if(seg == NULL)
|
|
break;
|
|
|
|
segnext = seg->next;
|
|
|
|
// ..
|
|
if(p->ondealloc != NULL){
|
|
// walk over the segment, and call dealloc callback!
|
|
ptr = (char*)seg;
|
|
ptr += ALIGN_TO_16(sizeof(struct pool_segment));
|
|
for(i = 0; i < seg->num_nodes_total; i++){
|
|
niter = (struct node*)ptr;
|
|
ptr += sizeof(struct node);
|
|
ptr += p->elem_size;
|
|
#ifdef MEMPOOLASSERT
|
|
if(niter->magic != NODE_MAGIC){
|
|
ShowError("Mempool [%s] Destroy - walk over segment - node %p invalid magic!\n", p->name, niter);
|
|
continue;
|
|
}
|
|
#endif
|
|
|
|
p->ondealloc( NODE_TO_DATA(niter) );
|
|
|
|
|
|
}
|
|
}//endif: ondealloc callback?
|
|
|
|
// simple ..
|
|
aFree(seg);
|
|
|
|
seg = segnext;
|
|
}
|
|
|
|
// Clear node ptr
|
|
p->free_list = NULL;
|
|
InterlockedExchange64(&p->num_nodes_free, 0);
|
|
InterlockedExchange64(&p->num_nodes_total, 0);
|
|
InterlockedExchange64(&p->num_segments, 0);
|
|
InterlockedExchange64(&p->num_bytes_total, 0);
|
|
|
|
LeaveSpinLock(&p->nodeLock);
|
|
LeaveSpinLock(&p->segmentLock);
|
|
|
|
// Free pool itself :D
|
|
aFree(p->name);
|
|
aFree(p);
|
|
|
|
}//end: mempool_destroy()
|
|
|
|
|
|
void *mempool_node_get(mempool p){
|
|
struct node *node;
|
|
int64 num_used;
|
|
|
|
if(p->num_nodes_free < p->elem_realloc_thresh)
|
|
racond_signal(l_async_cond);
|
|
|
|
while(1){
|
|
|
|
EnterSpinLock(&p->nodeLock);
|
|
|
|
node = p->free_list;
|
|
if(node != NULL)
|
|
p->free_list = node->next;
|
|
|
|
LeaveSpinLock(&p->nodeLock);
|
|
|
|
if(node != NULL)
|
|
break;
|
|
|
|
rathread_yield();
|
|
}
|
|
|
|
InterlockedDecrement64(&p->num_nodes_free);
|
|
|
|
// Update peak value
|
|
num_used = (p->num_nodes_total - p->num_nodes_free);
|
|
if(num_used > p->peak_nodes_used){
|
|
InterlockedExchange64(&p->peak_nodes_used, num_used);
|
|
}
|
|
|
|
#ifdef MEMPOOLASSERT
|
|
node->used = true;
|
|
#endif
|
|
|
|
return NODE_TO_DATA(node);
|
|
}//end: mempool_node_get()
|
|
|
|
|
|
void mempool_node_put(mempool p, void *data){
|
|
struct node *node;
|
|
|
|
node = DATA_TO_NODE(data);
|
|
#ifdef MEMPOOLASSERT
|
|
if(node->magic != NODE_MAGIC){
|
|
ShowError("Mempool [%s] node_put failed, given address (%p) has invalid magic.\n", p->name, data);
|
|
return; // lost,
|
|
}
|
|
|
|
{
|
|
struct pool_segment *node_seg = node->segment;
|
|
if(node_seg->pool != p){
|
|
ShowError("Mempool [%s] node_put faild, given node (data address %p) doesnt belongs to this pool. ( Node Origin is [%s] )\n", p->name, data, node_seg->pool);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// reset used flag.
|
|
node->used = false;
|
|
#endif
|
|
|
|
//
|
|
EnterSpinLock(&p->nodeLock);
|
|
node->next = p->free_list;
|
|
p->free_list = node;
|
|
LeaveSpinLock(&p->nodeLock);
|
|
|
|
InterlockedIncrement64(&p->num_nodes_free);
|
|
|
|
}//end: mempool_node_put()
|
|
|
|
|
|
mempool_stats mempool_get_stats(mempool pool){
|
|
mempool_stats stats;
|
|
|
|
// initialize all with zeros
|
|
memset(&stats, 0x00, sizeof(mempool_stats));
|
|
|
|
stats.num_nodes_total = pool->num_nodes_total;
|
|
stats.num_nodes_free = pool->num_nodes_free;
|
|
stats.num_nodes_used = (stats.num_nodes_total - stats.num_nodes_free);
|
|
stats.num_segments = pool->num_segments;
|
|
stats.num_realloc_events= pool->num_realloc_events;
|
|
stats.peak_nodes_used = pool->peak_nodes_used;
|
|
stats.num_bytes_total = pool->num_bytes_total;
|
|
|
|
// Pushing such a large block over the stack as return value isnt nice
|
|
// but lazy :) and should be okay in this case (Stats / Debug..)
|
|
// if you dont like it - feel free and refactor it.
|
|
return stats;
|
|
}//end: mempool_get_stats()
|
|
|