root/src/common/ers.c @ 22

Revision 1, 16.5 kB (checked in by jinshiro, 17 years ago)
Line 
1/*****************************************************************************\
2 *  Copyright (c) Athena 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 behaviours.                      *
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 fragmentaion - program will run better for longer.       *
24 *                                                                           *
25 *  <H2>Disavantages:</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 *                                                                           *
36 * @version 0.1 - Initial version                                            *
37 * @author Flavio @ Amazon Project                                           *
38 * @encoding US-ASCII                                                        *
39 * @see common#ers.h                                                         *
40\*****************************************************************************/
41#include <stdlib.h>
42
43#include "../common/cbasetypes.h"
44#include "../common/malloc.h" // CREATE, RECREATE, aMalloc, aFree
45#include "../common/showmsg.h" // ShowMessage, ShowError, ShowFatalError, CL_BOLD, CL_NORMAL
46#include "ers.h"
47
48#ifndef DISABLE_ERS
49/*****************************************************************************\
50 *  (1) Private defines, structures and global variables.                    *
51 *  ERS_BLOCK_ENTRIES - Number of entries in each block.                     *
52 *  ERS_ROOT_SIZE     - Maximum number of root entry managers.               *
53 *  ERLinkedList      - Structure of a linked list of reusable entries.      *
54 *  ERS_impl          - Class of an entry manager.                           *
55 *  ers_root          - Array of root entry managers.                        *
56 *  ers_num           - Number of root entry managers in the array.          *
57\*****************************************************************************/
58
59/**
60 * Number of entries in each block.
61 * @see #ers_obj_alloc_entry(ERS eri)
62 */
63#define ERS_BLOCK_ENTRIES 4096
64
65/**
66 * Maximum number of root entry managers.
67 * @private
68 * @see #ers_root
69 * @see #ers_num
70 */
71#define ERS_ROOT_SIZE 256
72
73/**
74 * Linked list of reusable entries.
75 * The minimum size of the entries is the size of this structure.
76 * @private
77 * @see ERS_impl#reuse
78 */
79typedef struct ers_ll {
80        struct ers_ll *next;
81} *ERLinkedList;
82
83/**
84 * Class of the object that manages entries of a certain size.
85 * @param eri Public interface of the object
86 * @param reuse Linked list of reusable data entries
87 * @param blocks Array with blocks of entries
88 * @param free Number of unused entries in the last block
89 * @param num Number of blocks in the array
90 * @param max Current maximum capacity of the array
91 * @param destroy Destroy lock
92 * @param size Size of the entries of the manager
93 * @private
94 */
95typedef struct ers_impl {
96
97        /**
98         * Public interface of the entry manager.
99         * @param alloc Allocate an entry from this manager
100         * @param free Free an entry allocated from this manager
101         * @param entry_size Return the size of the entries of this manager
102         * @param destroy Destroy this instance of the manager
103         * @public
104         */
105        struct eri vtable;
106
107        /**
108         * Linked list of reusable entries.
109         */
110        ERLinkedList reuse;
111
112        /**
113         * Array with blocks of entries.
114         */
115        uint8 **blocks;
116
117        /**
118         * Number of unused entries in the last block.
119         */
120        uint32 free;
121
122        /**
123         * Number of blocks in the array.
124         */
125        uint32 num;
126
127        /**
128         * Current maximum capacity of the array.
129         */
130        uint32 max;
131
132        /**
133         * Destroy lock.
134         */
135        uint32 destroy;
136
137        /**
138         * Size of the entries of the manager.
139         */
140        size_t size;
141
142} *ERS_impl;
143
144/**
145 * Root array with entry managers.
146 * @private
147 * @static
148 * @see #ERS_ROOT_SIZE
149 * @see #ers_num
150 */
151static ERS_impl ers_root[ERS_ROOT_SIZE];
152
153/**
154 * Number of entry managers in the root array.
155 * @private
156 * @static
157 * @see #ERS_ROOT_SIZE
158 * @see #ers_root
159 */
160static uint32 ers_num = 0;
161
162/*****************************************************************************\
163 *  (2) Object functions.                                                 *
164 *  ers_obj_alloc_entry - Allocate an entry from the manager.                *
165 *  ers_obj_free_entry  - Free an entry allocated from the manager.          *
166 *  ers_obj_entry_size  - Return the size of the entries of the manager.     *
167 *  ers_obj_destroy     - Destroy the instance of the manager.               *
168\*****************************************************************************/
169
170/**
171 * Allocate an entry from this entry manager.
172 * If there are reusable entries available, it reuses one instead.
173 * @param self Interface of the entry manager
174 * @return An entry
175 * @see #ERS_BLOCK_ENTRIES
176 * @see #ERLinkedList
177 * @see ERS_impl::vtable#alloc
178 */
179static void *ers_obj_alloc_entry(ERS self)
180{
181        ERS_impl obj = (ERS_impl)self;
182        void *ret;
183
184        if (obj == NULL) {
185                ShowError("ers::alloc : NULL object, aborting entry allocation.\n");
186                return NULL;
187        }
188
189        if (obj->reuse) { // Reusable entry
190                ret = obj->reuse;
191                obj->reuse = obj->reuse->next;
192        } else if (obj->free) { // Unused entry
193                obj->free--;
194                ret = &obj->blocks[obj->num -1][obj->free*obj->size];
195        } else { // allocate a new block
196                if (obj->num == obj->max) { // expand the block array
197                        if (obj->max == UINT32_MAX) { // No more space for blocks
198                                ShowFatalError("ers::alloc : maximum number of blocks reached, increase ERS_BLOCK_ENTRIES.\n"
199                                                "exiting the program...\n");
200                                exit(EXIT_FAILURE);
201                        }
202                        obj->max = (obj->max*4)+3; // left shift bits '11' - overflow won't happen
203                        RECREATE(obj->blocks, uint8 *, obj->max);
204                }
205                CREATE(obj->blocks[obj->num], uint8, obj->size*ERS_BLOCK_ENTRIES);
206                obj->free = ERS_BLOCK_ENTRIES -1;
207                ret = &obj->blocks[obj->num][obj->free*obj->size];
208                obj->num++;
209        }
210        return ret;
211}
212
213/**
214 * Free an entry allocated from this manager.
215 * WARNING: Does not check if the entry was allocated by this manager.
216 * Freeing such an entry can lead to unexpected behaviour.
217 * @param self Interface of the entry manager
218 * @param entry Entry to be freed
219 * @see #ERLinkedList
220 * @see ERS_impl#reuse
221 * @see ERS_impl::vtable#free
222 */
223static void ers_obj_free_entry(ERS self, void *entry)
224{
225        ERS_impl obj = (ERS_impl)self;
226        ERLinkedList reuse;
227
228        if (obj == NULL) {
229                ShowError("ers::free : NULL object, aborting entry freeing.\n");
230                return;
231        } else if (entry == NULL) {
232                ShowError("ers::free : NULL entry, nothing to free.\n");
233                return;
234        }
235
236        reuse = (ERLinkedList)entry;
237        reuse->next = obj->reuse;
238        obj->reuse = reuse;
239}
240
241/**
242 * Return the size of the entries allocated from this manager.
243 * @param self Interface of the entry manager
244 * @return Size of the entries of this manager in bytes
245 * @see ERS_impl#size
246 * @see ERS_impl::vtable#entry_size
247 */
248static size_t ers_obj_entry_size(ERS self)
249{
250        ERS_impl obj = (ERS_impl)self;
251
252        if (obj == NULL) {
253                ShowError("ers::entry_size : NULL object, returning 0.\n");
254                return 0;
255        }
256
257        return obj->size;
258}
259
260/**
261 * Destroy this instance of the manager.
262 * The manager is actually only destroyed when all the instances are destroyed.
263 * When destroying the manager a warning is shown if the manager has
264 * missing/extra entries.
265 * @param self Interface of the entry manager
266 * @see #ERLinkedList
267 * @see ERS_impl::vtable#destroy
268 */
269static void ers_obj_destroy(ERS self)
270{
271        ERS_impl obj = (ERS_impl)self;
272        ERLinkedList reuse,old;
273        uint32 i;
274        uint32 count;
275
276        if (obj == NULL) {
277                ShowError("ers::destroy: NULL object, aborting instance destruction.\n");
278                return;
279        }
280
281        obj->destroy--;
282        if (obj->destroy)
283                return; // Not last instance
284
285        // Remove manager from root array
286        for (i = 0; i < ers_num; i++) {
287                if (ers_root[i] == obj) {
288                        ers_num--;
289                        if (i < ers_num) // put the last manager in the free slot
290                                ers_root[i] = ers_root[ers_num];
291                        break;
292                }
293        }
294        reuse = obj->reuse;
295        count = 0;
296        // Check for missing/extra entries
297        for (i = 0; i < obj->num; i++) {
298                if (i == 0) {
299                        count = ERS_BLOCK_ENTRIES -obj->free;
300                } else if (count > UINT32_MAX -ERS_BLOCK_ENTRIES) {
301                        count = UINT32_MAX;
302                        break;
303                } else {
304                        count += ERS_BLOCK_ENTRIES;
305                }
306                while (reuse && count) {
307                        count--;
308                        old = reuse;
309                        reuse = reuse->next;
310                        old->next = NULL; // this makes duplicate frees report as missing entries
311                }
312        }
313        if (count) { // missing entries
314                ShowWarning("ers::destroy : %u entries missing (possible double free), continuing destruction (entry size=%u).",
315                                count, obj->size);
316        } else if (reuse) { // extra entries
317                while (reuse && count != UINT32_MAX) {
318                        count++;
319                        reuse = reuse->next;
320                }
321                ShowWarning("ers::destroy : %u extra entries found, continuing destruction (entry size=%u).",
322                                count, obj->size);
323        }
324        // destroy the entry manager
325        if (obj->max) {
326                for (i = 0; i < obj->num; i++)
327                        aFree(obj->blocks[i]); // release block of entries
328                aFree(obj->blocks); // release array of blocks
329        }
330        aFree(obj); // release manager
331}
332
333/*****************************************************************************\
334 *  (3) Public functions.                                                    *
335 *  ers_new               - Get a new instance of an entry manager.          *
336 *  ers_report            - Print a report about the current state.          *
337 *  ers_force_destroy_all - Force the destruction of all the managers.       *
338\*****************************************************************************/
339
340/**
341 * Get a new instance of the manager that handles the specified entry size.
342 * Size has to greater than 0.
343 * If the specified size is smaller than a pointer, the size of a pointer is
344 * used instead.
345 * It's also aligned to ERS_ALIGNED bytes, so the smallest multiple of
346 * ERS_ALIGNED that is greater or equal to size is what's actually used.
347 * @param The requested size of the entry in bytes
348 * @return Interface of the object
349 * @see #ERS_impl
350 * @see #ers_root
351 * @see #ers_num
352 */
353ERS ers_new(uint32 size)
354{
355        ERS_impl obj;
356        uint32 i;
357
358        if (size == 0) {
359                ShowError("ers_new: invalid size %u, aborting instance creation.\n",
360                                size);
361                return NULL;
362        }
363
364        if (size < sizeof(struct ers_ll)) // Minimum size
365                size = sizeof(struct ers_ll);
366        if (size%ERS_ALIGNED) // Align size
367                size += ERS_ALIGNED -size%ERS_ALIGNED;
368
369        for (i = 0; i < ers_num; i++) {
370                obj = ers_root[i];
371                if (obj->size == size) {
372                        // found a manager that handles the entry size
373                        obj->destroy++;
374                        return &obj->vtable;
375                }
376        }
377        // create a new manager to handle the entry size
378        if (ers_num == ERS_ROOT_SIZE) {
379                ShowFatalError("ers_alloc: too many root objects, increase ERS_ROOT_SIZE.\n"
380                                "exiting the program...\n");
381                exit(EXIT_FAILURE);
382        }
383        obj = (ERS_impl)aMalloc(sizeof(struct ers_impl));
384        // Public interface
385        obj->vtable.alloc      = ers_obj_alloc_entry;
386        obj->vtable.free       = ers_obj_free_entry;
387        obj->vtable.entry_size = ers_obj_entry_size;
388        obj->vtable.destroy    = ers_obj_destroy;
389        // Block reusage system
390        obj->reuse   = NULL;
391        obj->blocks  = NULL;
392        obj->free    = 0;
393        obj->num     = 0;
394        obj->max     = 0;
395        obj->destroy = 1;
396        // Properties
397        obj->size = size;
398        ers_root[ers_num++] = obj;
399        return &obj->vtable;
400}
401
402/**
403 * Print a report about the current state of the Entry Reusage System.
404 * Shows information about the global system and each entry manager.
405 * The number of entries are checked and a warning is shown if extra reusable
406 * entries are found.
407 * The extra entries are included in the count of reusable entries.
408 * @see #ERLinkedList
409 * @see #ERS_impl
410 * @see #ers_root
411 * @see #ers_num
412 */
413void ers_report(void)
414{
415        uint32 i;
416        uint32 j;
417        uint32 used;
418        uint32 reusable;
419        uint32 extra;
420        ERLinkedList reuse;
421        ERS_impl obj;
422
423        // Root system report
424        ShowMessage(CL_BOLD"Entry Reusage System report:\n"CL_NORMAL);
425        ShowMessage("root array size     : %u\n", ERS_ROOT_SIZE);
426        ShowMessage("root entry managers : %u\n", ers_num);
427        ShowMessage("entries per block   : %u\n", ERS_BLOCK_ENTRIES);
428        for (i = 0; i < ers_num; i++) {
429                obj = ers_root[i];
430                reuse = obj->reuse;
431                used = 0;
432                reusable = 0;
433                // Count used and reusable entries
434                for (j = 0; j < obj->num; j++) {
435                        if (j == 0) { // take into acount the free entries
436                                used = ERS_BLOCK_ENTRIES -obj->free;
437                        } else if (reuse) { // counting reusable entries
438                                used = ERS_BLOCK_ENTRIES;
439                        } else { // no more reusable entries, count remaining used entries
440                                for (; j < obj->num; j++) {
441                                        if (used > UINT32_MAX -ERS_BLOCK_ENTRIES) { // overflow
442                                                used = UINT32_MAX;
443                                                break;
444                                        }
445                                        used += ERS_BLOCK_ENTRIES;
446                                }
447                                break;
448                        }
449                        while (used && reuse) { // count reusable entries
450                                used--;
451                                if (reusable != UINT32_MAX)
452                                        reusable++;
453                                reuse = reuse->next;
454                        }
455                }
456                // Count extra reusable entries
457                extra = 0;
458                while (reuse && extra != UINT32_MAX) {
459                        extra++;
460                        reuse = reuse->next;
461                }
462                // Entry manager report
463                ShowMessage(CL_BOLD"[Entry manager #%u report]\n"CL_NORMAL, i);
464                ShowMessage("\tinstances          : %u\n", obj->destroy);
465                ShowMessage("\tentry size         : %u\n", obj->size);
466                ShowMessage("\tblock array size   : %u\n", obj->max);
467                ShowMessage("\tallocated blocks   : %u\n", obj->num);
468                ShowMessage("\tentries being used : %u\n", used);
469                ShowMessage("\tunused entries     : %u\n", obj->free);
470                ShowMessage("\treusable entries   : %u\n", reusable);
471                if (extra)
472                        ShowMessage("\tWARNING - %u extra reusable entries were found.\n", extra);
473        }
474        ShowMessage("End of report\n");
475}
476
477/**
478 * Forcibly destroy all the entry managers, checking for nothing.
479 * The system is left as if no instances or entries had ever been allocated.
480 * All previous entries and instances of the managers become invalid.
481 * The use of this is NOT recommended.
482 * It should only be used in extreme situations to make shure all the memory
483 * allocated by this system is released.
484 * @see #ERS_impl
485 * @see #ers_root
486 * @see #ers_num
487 */
488void ers_force_destroy_all(void)
489{
490        uint32 i;
491        uint32 j;
492        ERS_impl obj;
493
494        for (i = 0; i < ers_num; i++) {
495                obj = ers_root[i];
496                if (obj->max) {
497                        for (j = 0; j < obj->num; j++)
498                                aFree(obj->blocks[j]); // block of entries
499                        aFree(obj->blocks); // array of blocks
500                }
501                aFree(obj); // entry manager object
502        }
503        ers_num = 0;
504}
505#endif /* not DISABLE_ERS */
Note: See TracBrowser for help on using the browser.