ers.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. /*****************************************************************************\
  2. * Copyright (c) rAthena Dev Teams - Licensed under GNU GPL *
  3. * For more information, see LICENCE in the main folder *
  4. * *
  5. * <H1>Entry Reusage System</H1> *
  6. * *
  7. * There are several root entry managers, each with a different entry size. *
  8. * Each manager will keep track of how many instances have been 'created'. *
  9. * They will only automatically destroy themselves after the last instance *
  10. * is destroyed. *
  11. * *
  12. * Entries can be allocated from the managers. *
  13. * If it has reusable entries (freed entry), it uses one. *
  14. * So no assumption should be made about the data of the entry. *
  15. * Entries should be freed in the manager they where allocated from. *
  16. * Failure to do so can lead to unexpected behaviors. *
  17. * *
  18. * <H2>Advantages:</H2> *
  19. * - The same manager is used for entries of the same size. *
  20. * So entries freed in one instance of the manager can be used by other *
  21. * instances of the manager. *
  22. * - Much less memory allocation/deallocation - program will be faster. *
  23. * - Avoids memory fragmentation - program will run better for longer. *
  24. * *
  25. * <H2>Disadvantages:</H2> *
  26. * - Unused entries are almost inevitable - memory being wasted. *
  27. * - A manager will only auto-destroy when all of its instances are *
  28. * destroyed so memory will usually only be recovered near the end. *
  29. * - Always wastes space for entries smaller than a pointer. *
  30. * *
  31. * WARNING: The system is not thread-safe at the moment. *
  32. * *
  33. * HISTORY: *
  34. * 0.1 - Initial version *
  35. * 1.0 - ERS Rework *
  36. * *
  37. * @version 1.0 - ERS Rework *
  38. * @author GreenBox @ rAthena Project *
  39. * @encoding US-ASCII *
  40. * @see common#ers.hpp *
  41. \*****************************************************************************/
  42. #include "ers.hpp"
  43. #include <stdlib.h>
  44. #include <string.h>
  45. #include "cbasetypes.hpp"
  46. #include "malloc.hpp" // CREATE, RECREATE, aMalloc, aFree
  47. #include "nullpo.hpp"
  48. #include "showmsg.hpp" // ShowMessage, ShowError, ShowFatalError, CL_BOLD, CL_NORMAL
  49. #ifndef DISABLE_ERS
  50. #define ERS_BLOCK_ENTRIES 2048
  51. struct ers_list
  52. {
  53. struct ers_list *Next;
  54. };
  55. struct ers_instance_t;
  56. typedef struct ers_cache
  57. {
  58. // Allocated object size, including ers_list size
  59. unsigned int ObjectSize;
  60. // Number of ers_instances referencing this
  61. int ReferenceCount;
  62. // Reuse linked list
  63. struct ers_list *ReuseList;
  64. // Memory blocks array
  65. unsigned char **Blocks;
  66. // Max number of blocks
  67. unsigned int Max;
  68. // Free objects count
  69. unsigned int Free;
  70. // Used blocks count
  71. unsigned int Used;
  72. // Objects in-use count
  73. unsigned int UsedObjs;
  74. // Default = ERS_BLOCK_ENTRIES, can be adjusted for performance for individual cache sizes.
  75. unsigned int ChunkSize;
  76. // Misc options, some options are shared from the instance
  77. enum ERSOptions Options;
  78. // Linked list
  79. struct ers_cache *Next, *Prev;
  80. } ers_cache_t;
  81. struct ers_instance_t {
  82. // Interface to ERS
  83. struct eri VTable;
  84. // Name, used for debugging purposes
  85. char *Name;
  86. // Misc options
  87. enum ERSOptions Options;
  88. // Our cache
  89. ers_cache_t *Cache;
  90. // Count of objects in use, used for detecting memory leaks
  91. unsigned int Count;
  92. struct ers_instance_t *Next, *Prev;
  93. };
  94. // Array containing a pointer for all ers_cache structures
  95. static ers_cache_t *CacheList = NULL;
  96. static struct ers_instance_t *InstanceList = NULL;
  97. /**
  98. * @param Options the options from the instance seeking a cache, we use it to give it a cache with matching configuration
  99. **/
  100. static ers_cache_t *ers_find_cache(unsigned int size, enum ERSOptions Options) {
  101. ers_cache_t *cache;
  102. for (cache = CacheList; cache; cache = cache->Next)
  103. if ( cache->ObjectSize == size && cache->Options == ( Options & ERS_CACHE_OPTIONS ) )
  104. return cache;
  105. CREATE(cache, ers_cache_t, 1);
  106. cache->ObjectSize = size;
  107. cache->ReferenceCount = 0;
  108. cache->ReuseList = NULL;
  109. cache->Blocks = NULL;
  110. cache->Free = 0;
  111. cache->Used = 0;
  112. cache->UsedObjs = 0;
  113. cache->Max = 0;
  114. cache->ChunkSize = ERS_BLOCK_ENTRIES;
  115. cache->Options = (enum ERSOptions)(Options & ERS_CACHE_OPTIONS);
  116. if (CacheList == NULL)
  117. {
  118. CacheList = cache;
  119. }
  120. else
  121. {
  122. cache->Next = CacheList;
  123. cache->Next->Prev = cache;
  124. CacheList = cache;
  125. CacheList->Prev = NULL;
  126. }
  127. return cache;
  128. }
  129. static void ers_free_cache(ers_cache_t *cache, bool remove)
  130. {
  131. unsigned int i;
  132. for (i = 0; i < cache->Used; i++)
  133. aFree(cache->Blocks[i]);
  134. if (cache->Next)
  135. cache->Next->Prev = cache->Prev;
  136. if (cache->Prev)
  137. cache->Prev->Next = cache->Next;
  138. else
  139. CacheList = cache->Next;
  140. aFree(cache->Blocks);
  141. aFree(cache);
  142. }
  143. static void *ers_obj_alloc_entry(ERS *self)
  144. {
  145. struct ers_instance_t *instance = (struct ers_instance_t *)self;
  146. void *ret;
  147. if (instance == NULL) {
  148. ShowError("ers_obj_alloc_entry: NULL object, aborting entry freeing.\n");
  149. return NULL;
  150. }
  151. if (instance->Cache->ReuseList != NULL) {
  152. ret = (void *)((unsigned char *)instance->Cache->ReuseList + sizeof(struct ers_list));
  153. instance->Cache->ReuseList = instance->Cache->ReuseList->Next;
  154. } else if (instance->Cache->Free > 0) {
  155. instance->Cache->Free--;
  156. ret = &instance->Cache->Blocks[instance->Cache->Used - 1][static_cast<size_t>( instance->Cache->Free ) * static_cast<size_t>( instance->Cache->ObjectSize ) + sizeof( struct ers_list )];
  157. } else {
  158. if (instance->Cache->Used == instance->Cache->Max) {
  159. instance->Cache->Max = (instance->Cache->Max * 4) + 3;
  160. RECREATE(instance->Cache->Blocks, unsigned char *, instance->Cache->Max);
  161. }
  162. CREATE(instance->Cache->Blocks[instance->Cache->Used], unsigned char, instance->Cache->ObjectSize * instance->Cache->ChunkSize);
  163. instance->Cache->Used++;
  164. instance->Cache->Free = instance->Cache->ChunkSize -1;
  165. ret = &instance->Cache->Blocks[instance->Cache->Used - 1][static_cast<size_t>( instance->Cache->Free ) * static_cast<size_t>( instance->Cache->ObjectSize ) + sizeof( struct ers_list )];
  166. }
  167. instance->Count++;
  168. instance->Cache->UsedObjs++;
  169. return ret;
  170. }
  171. static void ers_obj_free_entry(ERS *self, void *entry)
  172. {
  173. struct ers_instance_t *instance = (struct ers_instance_t *)self;
  174. struct ers_list *reuse = (struct ers_list *)((unsigned char *)entry - sizeof(struct ers_list));
  175. if (instance == NULL) {
  176. ShowError("ers_obj_free_entry: NULL object, aborting entry freeing.\n");
  177. return;
  178. } else if (entry == NULL) {
  179. ShowError("ers_obj_free_entry: NULL entry, nothing to free.\n");
  180. return;
  181. }
  182. if( instance->Cache->Options & ERS_OPT_CLEAN )
  183. memset((unsigned char*)reuse + sizeof(struct ers_list), 0, instance->Cache->ObjectSize - sizeof(struct ers_list));
  184. reuse->Next = instance->Cache->ReuseList;
  185. instance->Cache->ReuseList = reuse;
  186. instance->Count--;
  187. instance->Cache->UsedObjs--;
  188. }
  189. static size_t ers_obj_entry_size(ERS *self)
  190. {
  191. struct ers_instance_t *instance = (struct ers_instance_t *)self;
  192. if (instance == NULL) {
  193. ShowError("ers_obj_entry_size: NULL object, aborting entry freeing.\n");
  194. return 0;
  195. }
  196. return instance->Cache->ObjectSize;
  197. }
  198. static void ers_obj_destroy(ERS *self)
  199. {
  200. struct ers_instance_t *instance = (struct ers_instance_t *)self;
  201. if (instance == NULL) {
  202. ShowError("ers_obj_destroy: NULL object, aborting entry freeing.\n");
  203. return;
  204. }
  205. if (instance->Count > 0)
  206. if (!(instance->Options & ERS_OPT_CLEAR))
  207. ShowWarning("Memory leak detected at ERS '%s', %d objects not freed.\n", instance->Name, instance->Count);
  208. if (--instance->Cache->ReferenceCount <= 0)
  209. ers_free_cache(instance->Cache, true);
  210. if (instance->Next)
  211. instance->Next->Prev = instance->Prev;
  212. if (instance->Prev)
  213. instance->Prev->Next = instance->Next;
  214. else
  215. InstanceList = instance->Next;
  216. if( instance->Options & ERS_OPT_FREE_NAME )
  217. aFree(instance->Name);
  218. aFree(instance);
  219. }
  220. void ers_cache_size(ERS *self, unsigned int new_size) {
  221. struct ers_instance_t *instance = (struct ers_instance_t *)self;
  222. nullpo_retv(instance);
  223. if( !(instance->Cache->Options&ERS_OPT_FLEX_CHUNK) ) {
  224. ShowWarning("ers_cache_size: '%s' has adjusted its chunk size to '%d', however ERS_OPT_FLEX_CHUNK is missing!\n",instance->Name,new_size);
  225. }
  226. instance->Cache->ChunkSize = new_size;
  227. }
  228. ERS *ers_new(uint32 size, const char *name, enum ERSOptions options)
  229. {
  230. struct ers_instance_t *instance;
  231. CREATE(instance,struct ers_instance_t, 1);
  232. size += sizeof(struct ers_list);
  233. #if ERS_ALIGNED > 1 // If it's aligned to 1-byte boundaries, no need to bother.
  234. if (size % ERS_ALIGNED)
  235. size += ERS_ALIGNED - size % ERS_ALIGNED;
  236. #endif
  237. instance->VTable.alloc = ers_obj_alloc_entry;
  238. instance->VTable.free = ers_obj_free_entry;
  239. instance->VTable.entry_size = ers_obj_entry_size;
  240. instance->VTable.destroy = ers_obj_destroy;
  241. instance->VTable.chunk_size = ers_cache_size;
  242. instance->Name = ( options & ERS_OPT_FREE_NAME ) ? (char *)aStrdup(name) : (char *)name;
  243. instance->Options = options;
  244. instance->Cache = ers_find_cache(size,instance->Options);
  245. instance->Cache->ReferenceCount++;
  246. if (InstanceList == NULL) {
  247. InstanceList = instance;
  248. } else {
  249. instance->Next = InstanceList;
  250. instance->Next->Prev = instance;
  251. InstanceList = instance;
  252. InstanceList->Prev = NULL;
  253. }
  254. instance->Count = 0;
  255. return &instance->VTable;
  256. }
  257. void ers_report(void) {
  258. ers_cache_t *cache;
  259. unsigned int cache_c = 0, blocks_u = 0, blocks_a = 0, memory_b = 0, memory_t = 0;
  260. for (cache = CacheList; cache; cache = cache->Next) {
  261. cache_c++;
  262. ShowMessage(CL_BOLD"[ERS Cache of size '" CL_NORMAL "" CL_WHITE "%u" CL_NORMAL "" CL_BOLD "' report]\n" CL_NORMAL, cache->ObjectSize);
  263. ShowMessage("\tinstances : %u\n", cache->ReferenceCount);
  264. ShowMessage("\tblocks in use : %u/%u\n", cache->UsedObjs, cache->UsedObjs+cache->Free);
  265. ShowMessage("\tblocks unused : %u\n", cache->Free);
  266. ShowMessage("\tmemory in use : %.2f MB\n", cache->UsedObjs == 0 ? 0. : (double)((cache->UsedObjs * cache->ObjectSize)/1024)/1024);
  267. ShowMessage("\tmemory allocated : %.2f MB\n", (cache->Free+cache->UsedObjs) == 0 ? 0. : (double)(((cache->UsedObjs+cache->Free) * cache->ObjectSize)/1024)/1024);
  268. blocks_u += cache->UsedObjs;
  269. blocks_a += cache->UsedObjs + cache->Free;
  270. memory_b += cache->UsedObjs * cache->ObjectSize;
  271. memory_t += (cache->UsedObjs+cache->Free) * cache->ObjectSize;
  272. }
  273. ShowInfo("ers_report: '" CL_WHITE "%u" CL_NORMAL "' caches in use\n",cache_c);
  274. ShowInfo("ers_report: '" CL_WHITE "%u" CL_NORMAL "' blocks in use, consuming '" CL_WHITE "%.2f MB" CL_NORMAL "'\n",blocks_u,(double)((memory_b)/1024)/1024);
  275. ShowInfo("ers_report: '" CL_WHITE "%u" CL_NORMAL "' blocks total, consuming '" CL_WHITE "%.2f MB" CL_NORMAL "' \n",blocks_a,(double)((memory_t)/1024)/1024);
  276. }
  277. /**
  278. * Call on shutdown to clear remaining entries
  279. **/
  280. void ers_final(void) {
  281. struct ers_instance_t *instance = InstanceList, *next;
  282. while( instance ) {
  283. next = instance->Next;
  284. ers_obj_destroy((ERS*)instance);
  285. instance = next;
  286. }
  287. }
  288. #endif