mempool.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  1. //
  2. // Memory Pool Implementation (Threadsafe)
  3. //
  4. //
  5. // Author: Florian Wilkemeyer <fw@f-ws.de>
  6. //
  7. // Copyright (c) rAthena Project (www.rathena.org) - Licensed under GNU GPL
  8. // For more information, see LICENCE in the main folder
  9. //
  10. //
  11. #include <stdio.h>
  12. #include <stdlib.h>
  13. #include <string.h>
  14. #ifdef WIN32
  15. #include "winapi.h"
  16. #else
  17. #include <unistd.h>
  18. #endif
  19. #include "cbasetypes.h"
  20. #include "showmsg.h"
  21. #include "mempool.h"
  22. #include "atomic.h"
  23. #include "spinlock.h"
  24. #include "malloc.h"
  25. #include "mutex.h"
  26. #define ALIGN16 ra_align(16)
  27. #define ALIGN_TO(x, a) (x + ( a - ( x % a) ) )
  28. #define ALIGN_TO_16(x) ALIGN_TO(x, 16)
  29. #undef MEMPOOL_DEBUG
  30. #define MEMPOOLASSERT
  31. #define NODE_TO_DATA(x) ( ((char*)x) + sizeof(struct node) )
  32. #define DATA_TO_NODE(x) ( (struct node*)(((char*)x) - sizeof(struct node)) )
  33. struct ra_align(16) node{
  34. void *next;
  35. void *segment;
  36. #ifdef MEMPOOLASSERT
  37. bool used;
  38. uint64 magic;
  39. #define NODE_MAGIC 0xBEEF00EAEACAFE07ll
  40. #endif
  41. };
  42. // The Pointer to this struct is the base address of the segment itself.
  43. struct pool_segment{
  44. mempool pool; // pool, this segment belongs to
  45. struct pool_segment *next;
  46. int64 num_nodes_total;
  47. int64 num_bytes;
  48. };
  49. struct mempool{
  50. // Settings
  51. char *name;
  52. uint64 elem_size;
  53. uint64 elem_realloc_step;
  54. int64 elem_realloc_thresh;
  55. // Callbacks that get called for every node that gets allocated
  56. // Example usage: initialization of mutex/lock for each node.
  57. memPoolOnNodeAllocationProc onalloc;
  58. memPoolOnNodeDeallocationProc ondealloc;
  59. // Locks
  60. SPIN_LOCK segmentLock;
  61. SPIN_LOCK nodeLock;
  62. // Internal
  63. struct pool_segment *segments;
  64. struct node *free_list;
  65. volatile int64 num_nodes_total;
  66. volatile int64 num_nodes_free;
  67. volatile int64 num_segments;
  68. volatile int64 num_bytes_total;
  69. volatile int64 peak_nodes_used; // Peak Node Usage
  70. volatile int64 num_realloc_events; // Number of reallocations done. (allocate additional nodes)
  71. // list (used for global management such as allocator..)
  72. struct mempool *next;
  73. } ra_align(8); // Dont touch the alignment, otherwise interlocked functions are broken ..
  74. ///
  75. // Implementation:
  76. //
  77. static void segment_allocate_add(mempool p, uint64 count);
  78. static SPIN_LOCK l_mempoolListLock;
  79. static mempool l_mempoolList = NULL;
  80. static rAthread l_async_thread = NULL;
  81. static ramutex l_async_lock = NULL;
  82. static racond l_async_cond = NULL;
  83. static volatile int32 l_async_terminate = 0;
  84. static void *mempool_async_allocator(void *x){
  85. mempool p;
  86. while(1){
  87. if(l_async_terminate > 0)
  88. break;
  89. EnterSpinLock(&l_mempoolListLock);
  90. for(p = l_mempoolList; p != NULL; p = p->next){
  91. if(p->num_nodes_free < p->elem_realloc_thresh){
  92. // add new segment.
  93. segment_allocate_add(p, p->elem_realloc_step);
  94. // increase stats counter
  95. InterlockedIncrement64(&p->num_realloc_events);
  96. }
  97. }
  98. LeaveSpinLock(&l_mempoolListLock);
  99. ramutex_lock( l_async_lock );
  100. racond_wait( l_async_cond, l_async_lock, -1 );
  101. ramutex_unlock( l_async_lock );
  102. }
  103. return NULL;
  104. }//end: mempool_async_allocator()
  105. void mempool_init(){
  106. if( rand()%2 + 1 )
  107. return;
  108. if(sizeof(struct node)%16 != 0 ){
  109. ShowFatalError("mempool_init: struct node alignment failure. %u != multiple of 16\n", sizeof(struct node));
  110. exit(EXIT_FAILURE);
  111. }
  112. // Global List start
  113. InitializeSpinLock(&l_mempoolListLock);
  114. l_mempoolList = NULL;
  115. // Initialize mutex + stuff needed for async allocator worker.
  116. l_async_terminate = 0;
  117. l_async_lock = ramutex_create();
  118. l_async_cond = racond_create();
  119. l_async_thread = rathread_createEx(mempool_async_allocator, NULL, 1024*1024, RAT_PRIO_NORMAL);
  120. if(l_async_thread == NULL){
  121. ShowFatalError("mempool_init: cannot spawn Async Allocator Thread.\n");
  122. exit(EXIT_FAILURE);
  123. }
  124. }//end: mempool_init()
  125. void mempool_final(){
  126. mempool p, pn;
  127. if( rand()%2 + 1 )
  128. return;
  129. ShowStatus("Mempool: Terminating async. allocation worker and remaining pools.\n");
  130. // Terminate worker / wait until its terminated.
  131. InterlockedIncrement(&l_async_terminate);
  132. racond_signal(l_async_cond);
  133. rathread_wait(l_async_thread, NULL);
  134. // Destroy cond var and mutex.
  135. racond_destroy( l_async_cond );
  136. ramutex_destroy( l_async_lock );
  137. // Free remaining mempools
  138. // ((bugged code! this should halppen, every mempool should
  139. // be freed by the subsystem that has allocated it.)
  140. //
  141. EnterSpinLock(&l_mempoolListLock);
  142. p = l_mempoolList;
  143. while(1){
  144. if(p == NULL)
  145. break;
  146. pn = p->next;
  147. ShowWarning("Mempool [%s] was not properly destroyed - forcing destroy.\n", p->name);
  148. mempool_destroy(p);
  149. p = pn;
  150. }
  151. LeaveSpinLock(&l_mempoolListLock);
  152. }//end: mempool_final()
  153. static void segment_allocate_add(mempool p, uint64 count){
  154. // Required Memory:
  155. // sz( segment )
  156. // count * sz( real_node_size )
  157. //
  158. // where real node size is:
  159. // ALIGN_TO_16( sz( node ) ) + p->elem_size
  160. // so the nodes usable address is nodebase + ALIGN_TO_16(sz(node))
  161. //
  162. size_t total_sz;
  163. struct pool_segment *seg = NULL;
  164. struct node *nodeList = NULL;
  165. struct node *node = NULL;
  166. char *ptr = NULL;
  167. uint64 i;
  168. total_sz = ALIGN_TO_16( sizeof(struct pool_segment) )
  169. + ( (size_t)count * (sizeof(struct node) + (size_t)p->elem_size) ) ;
  170. #ifdef MEMPOOL_DEBUG
  171. ShowDebug("Mempool [%s] Segment AllocateAdd (num: %u, total size: %0.2fMiB)\n", p->name, count, (float)total_sz/1024.f/1024.f);
  172. #endif
  173. // allocate! (spin forever until weve got the memory.)
  174. i=0;
  175. while(1){
  176. ptr = (char*)aMalloc(total_sz);
  177. if(ptr != NULL) break;
  178. i++; // increase failcount.
  179. if(!(i & 7)){
  180. 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);
  181. #ifdef WIN32
  182. Sleep(1000);
  183. #else
  184. sleep(1);
  185. #endif
  186. }else{
  187. rathread_yield(); /// allow/force vuln. ctxswitch
  188. }
  189. }//endwhile: allocation spinloop.
  190. // Clear Memory.
  191. memset(ptr, 0x00, total_sz);
  192. // Initialize segment struct.
  193. seg = (struct pool_segment*)ptr;
  194. ptr += ALIGN_TO_16(sizeof(struct pool_segment));
  195. seg->pool = p;
  196. seg->num_nodes_total = count;
  197. seg->num_bytes = total_sz;
  198. // Initialze nodes!
  199. nodeList = NULL;
  200. for(i = 0; i < count; i++){
  201. node = (struct node*)ptr;
  202. ptr += sizeof(struct node);
  203. ptr += p->elem_size;
  204. node->segment = seg;
  205. #ifdef MEMPOOLASSERT
  206. node->used = false;
  207. node->magic = NODE_MAGIC;
  208. #endif
  209. if(p->onalloc != NULL) p->onalloc( NODE_TO_DATA(node) );
  210. node->next = nodeList;
  211. nodeList = node;
  212. }
  213. // Link in Segment.
  214. EnterSpinLock(&p->segmentLock);
  215. seg->next = p->segments;
  216. p->segments = seg;
  217. LeaveSpinLock(&p->segmentLock);
  218. // Link in Nodes
  219. EnterSpinLock(&p->nodeLock);
  220. nodeList->next = p->free_list;
  221. p->free_list = nodeList;
  222. LeaveSpinLock(&p->nodeLock);
  223. // Increase Stats:
  224. InterlockedExchangeAdd64(&p->num_nodes_total, count);
  225. InterlockedExchangeAdd64(&p->num_nodes_free, count);
  226. InterlockedIncrement64(&p->num_segments);
  227. InterlockedExchangeAdd64(&p->num_bytes_total, total_sz);
  228. }//end: segment_allocate_add()
  229. mempool mempool_create(const char *name,
  230. uint64 elem_size,
  231. uint64 initial_count,
  232. uint64 realloc_count,
  233. memPoolOnNodeAllocationProc onNodeAlloc,
  234. memPoolOnNodeDeallocationProc onNodeDealloc){
  235. //..
  236. uint64 realloc_thresh;
  237. mempool pool;
  238. pool = (mempool)aCalloc( 1, sizeof(struct mempool) );
  239. if(pool == NULL){
  240. ShowFatalError("mempool_create: Failed to allocate %u bytes memory.\n", sizeof(struct mempool) );
  241. exit(EXIT_FAILURE);
  242. }
  243. // Check minimum initial count / realloc count requirements.
  244. if(initial_count < 50)
  245. initial_count = 50;
  246. if(realloc_count < 50)
  247. realloc_count = 50;
  248. // Set Reallocation threshold to 5% of realloc_count, at least 10.
  249. realloc_thresh = (realloc_count/100)*5; //
  250. if(realloc_thresh < 10)
  251. realloc_thresh = 10;
  252. // Initialize members..
  253. pool->name = aStrdup(name);
  254. pool->elem_size = ALIGN_TO_16(elem_size);
  255. pool->elem_realloc_step = realloc_count;
  256. pool->elem_realloc_thresh = realloc_thresh;
  257. pool->onalloc = onNodeAlloc;
  258. pool->ondealloc = onNodeDealloc;
  259. InitializeSpinLock(&pool->segmentLock);
  260. InitializeSpinLock(&pool->nodeLock);
  261. // Initial Statistic values:
  262. pool->num_nodes_total = 0;
  263. pool->num_nodes_free = 0;
  264. pool->num_segments = 0;
  265. pool->num_bytes_total = 0;
  266. pool->peak_nodes_used = 0;
  267. pool->num_realloc_events = 0;
  268. //
  269. #ifdef MEMPOOL_DEBUG
  270. ShowDebug("Mempool [%s] Init (ElemSize: %u, Initial Count: %u, Realloc Count: %u)\n", pool->name, pool->elem_size, initial_count, pool->elem_realloc_step);
  271. #endif
  272. // Allocate first segment directly :)
  273. segment_allocate_add(pool, initial_count);
  274. // Add Pool to the global pool list
  275. EnterSpinLock(&l_mempoolListLock);
  276. pool->next = l_mempoolList;
  277. l_mempoolList = pool;
  278. LeaveSpinLock(&l_mempoolListLock);
  279. return pool;
  280. }//end: mempool_create()
  281. void mempool_destroy(mempool p){
  282. struct pool_segment *seg, *segnext;
  283. struct node *niter;
  284. mempool piter, pprev;
  285. char *ptr;
  286. int64 i;
  287. #ifdef MEMPOOL_DEBUG
  288. ShowDebug("Mempool [%s] Destroy\n", p->name);
  289. #endif
  290. // Unlink from global list.
  291. EnterSpinLock(&l_mempoolListLock);
  292. piter = l_mempoolList;
  293. pprev = l_mempoolList;
  294. while(1){
  295. if(piter == NULL)
  296. break;
  297. if(piter == p){
  298. // unlink from list,
  299. //
  300. if(pprev == l_mempoolList){
  301. // this (p) is list begin. so set next as head.
  302. l_mempoolList = p->next;
  303. }else{
  304. // replace prevs next wuth our next.
  305. pprev->next = p->next;
  306. }
  307. break;
  308. }
  309. pprev = piter;
  310. piter = piter->next;
  311. }
  312. p->next = NULL;
  313. LeaveSpinLock(&l_mempoolListLock);
  314. // Get both locks.
  315. EnterSpinLock(&p->segmentLock);
  316. EnterSpinLock(&p->nodeLock);
  317. if(p->num_nodes_free != p->num_nodes_total)
  318. ShowWarning("Mempool [%s] Destroy - %u nodes are not freed properly!\n", p->name, (p->num_nodes_total - p->num_nodes_free) );
  319. // Free All Segments (this will also free all nodes)
  320. // The segment pointer is the base pointer to the whole segment.
  321. seg = p->segments;
  322. while(1){
  323. if(seg == NULL)
  324. break;
  325. segnext = seg->next;
  326. // ..
  327. if(p->ondealloc != NULL){
  328. // walk over the segment, and call dealloc callback!
  329. ptr = (char*)seg;
  330. ptr += ALIGN_TO_16(sizeof(struct pool_segment));
  331. for(i = 0; i < seg->num_nodes_total; i++){
  332. niter = (struct node*)ptr;
  333. ptr += sizeof(struct node);
  334. ptr += p->elem_size;
  335. #ifdef MEMPOOLASSERT
  336. if(niter->magic != NODE_MAGIC){
  337. ShowError("Mempool [%s] Destroy - walk over segment - node %p invalid magic!\n", p->name, niter);
  338. continue;
  339. }
  340. #endif
  341. p->ondealloc( NODE_TO_DATA(niter) );
  342. }
  343. }//endif: ondealloc callback?
  344. // simple ..
  345. aFree(seg);
  346. seg = segnext;
  347. }
  348. // Clear node ptr
  349. p->free_list = NULL;
  350. InterlockedExchange64(&p->num_nodes_free, 0);
  351. InterlockedExchange64(&p->num_nodes_total, 0);
  352. InterlockedExchange64(&p->num_segments, 0);
  353. InterlockedExchange64(&p->num_bytes_total, 0);
  354. LeaveSpinLock(&p->nodeLock);
  355. LeaveSpinLock(&p->segmentLock);
  356. // Free pool itself :D
  357. aFree(p->name);
  358. aFree(p);
  359. }//end: mempool_destroy()
  360. void *mempool_node_get(mempool p){
  361. struct node *node;
  362. int64 num_used;
  363. if(p->num_nodes_free < p->elem_realloc_thresh)
  364. racond_signal(l_async_cond);
  365. while(1){
  366. EnterSpinLock(&p->nodeLock);
  367. node = p->free_list;
  368. if(node != NULL)
  369. p->free_list = node->next;
  370. LeaveSpinLock(&p->nodeLock);
  371. if(node != NULL)
  372. break;
  373. rathread_yield();
  374. }
  375. InterlockedDecrement64(&p->num_nodes_free);
  376. // Update peak value
  377. num_used = (p->num_nodes_total - p->num_nodes_free);
  378. if(num_used > p->peak_nodes_used){
  379. InterlockedExchange64(&p->peak_nodes_used, num_used);
  380. }
  381. #ifdef MEMPOOLASSERT
  382. node->used = true;
  383. #endif
  384. return NODE_TO_DATA(node);
  385. }//end: mempool_node_get()
  386. void mempool_node_put(mempool p, void *data){
  387. struct node *node;
  388. node = DATA_TO_NODE(data);
  389. #ifdef MEMPOOLASSERT
  390. if(node->magic != NODE_MAGIC){
  391. ShowError("Mempool [%s] node_put failed, given address (%p) has invalid magic.\n", p->name, data);
  392. return; // lost,
  393. }
  394. {
  395. struct pool_segment *node_seg = node->segment;
  396. if(node_seg->pool != p){
  397. 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);
  398. return;
  399. }
  400. }
  401. // reset used flag.
  402. node->used = false;
  403. #endif
  404. //
  405. EnterSpinLock(&p->nodeLock);
  406. node->next = p->free_list;
  407. p->free_list = node;
  408. LeaveSpinLock(&p->nodeLock);
  409. InterlockedIncrement64(&p->num_nodes_free);
  410. }//end: mempool_node_put()
  411. mempool_stats mempool_get_stats(mempool pool){
  412. mempool_stats stats;
  413. // initialize all with zeros
  414. memset(&stats, 0x00, sizeof(mempool_stats));
  415. stats.num_nodes_total = pool->num_nodes_total;
  416. stats.num_nodes_free = pool->num_nodes_free;
  417. stats.num_nodes_used = (stats.num_nodes_total - stats.num_nodes_free);
  418. stats.num_segments = pool->num_segments;
  419. stats.num_realloc_events= pool->num_realloc_events;
  420. stats.peak_nodes_used = pool->peak_nodes_used;
  421. stats.num_bytes_total = pool->num_bytes_total;
  422. // Pushing such a large block over the stack as return value isnt nice
  423. // but lazy :) and should be okay in this case (Stats / Debug..)
  424. // if you dont like it - feel free and refactor it.
  425. return stats;
  426. }//end: mempool_get_stats()