From 8ac0d539d059fdcd1a810ac056fa2c1a77773490 Mon Sep 17 00:00:00 2001 From: Vincent Wei Date: Thu, 4 Apr 2019 12:18:10 +0800 Subject: [PATCH] implementation of mg_slice_xxx --- configure.ac | 3 +- include/common.h | 56 +- include/minigui.h | 321 +++++++++ src/include/misc.h | 3 + src/kernel/fixstr.c | 46 +- src/kernel/init-lite.c | 19 +- src/kernel/init.c | 10 +- src/libc/Makefile.am | 8 +- src/libc/mgslice.c | 1432 ++++++++++++++++++++++++++++++++++++++++ src/misc/misc.c | 2 +- 10 files changed, 1856 insertions(+), 44 deletions(-) create mode 100644 src/libc/mgslice.c diff --git a/configure.ac b/configure.ac index 8d73772d..0124a217 100644 --- a/configure.ac +++ b/configure.ac @@ -76,7 +76,8 @@ AC_FUNC_ALLOCA AC_FUNC_MEMCMP AC_FUNC_MMAP AC_FUNC_VPRINTF -AC_CHECK_FUNCS(time mktime localtime strdup strcasecmp strncasecmp strerror setlocale getpt) +AC_CHECK_FUNCS(time mktime localtime strdup strcasecmp strncasecmp strerror setlocale) +AC_CHECK_FUNCS(posix_memalign memalign valloc) dnl ======================================================================== dnl User selectable options diff --git a/include/common.h b/include/common.h index c5a4e83c..b0d8dc28 100644 --- a/include/common.h +++ b/include/common.h @@ -169,10 +169,10 @@ typedef signed int Sint32; /* Figure out how to support 64-bit datatypes */ #if !defined(__STRICT_ANSI__) # if defined(__GNUC__) -# define MGUI_HAS_64BIT_TYPE long long +# define MGUI_HAS_64BIT_TYPE long long # endif # if defined(__CC_ARM) -# define MGUI_HAS_64BIT_TYPE long long +# define MGUI_HAS_64BIT_TYPE long long # endif # if defined(_MSC_VER) # define MGUI_HAS_64BIT_TYPE __int64 @@ -199,8 +199,8 @@ typedef signed MGUI_HAS_64BIT_TYPE Sint64; #else /* This is really just a hack to prevent the compiler from complaining */ typedef struct { - Uint32 hi; - Uint32 lo; + Uint32 hi; + Uint32 lo; } Uint64, Sint64; #endif @@ -221,6 +221,38 @@ MGUI_COMPILE_TIME_ASSERT(sint64, sizeof(Sint64) == 8); /** @} end of basic_types */ +/* Here we provide MG_GNUC_EXTENSION as an alias for __extension__, + * where this is valid. This allows for warningless compilation of + * "long long" types even in the presence of '-ansi -pedantic'. + */ +#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 8) +#define MG_GNUC_EXTENSION __extension__ +#else +#define MG_GNUC_EXTENSION +#endif + +/* + * The MG_LIKELY and MG_UNLIKELY macros let the programmer give hints to + * the compiler about the expected result of an expression. Some compilers + * can use this information for optimizations. + */ +#if defined(__GNUC__) && (__GNUC__ > 2) && defined(__OPTIMIZE__) +#define _MG_BOOLEAN_EXPR(expr) \ + MG_GNUC_EXTENSION ({ \ + int _g_boolean_var_; \ + if (expr) \ + _g_boolean_var_ = 1; \ + else \ + _g_boolean_var_ = 0; \ + _g_boolean_var_; \ +}) +#define MG_LIKELY(expr) (__builtin_expect (_MG_BOOLEAN_EXPR(expr), 1)) +#define MG_UNLIKELY(expr) (__builtin_expect (_MG_BOOLEAN_EXPR(expr), 0)) +#else +#define MG_LIKELY(expr) (expr) +#define MG_UNLIKELY(expr) (expr) +#endif + /** * \defgroup endian_info Endianness information * @{ @@ -1778,12 +1810,12 @@ struct tm { #include "os_type.h" #include "os_file_api.h" -#define fopen tp_fopen -#define fclose tp_fclose -#define fwrite tp_fwrite -#define fread tp_fread -#define fseek tp_fseek -#define feof tp_feof +#define fopen tp_fopen +#define fclose tp_fclose +#define fwrite tp_fwrite +#define fread tp_fread +#define fseek tp_fseek +#define feof tp_feof #undef assert #define _HAVE_ASSERT 1 @@ -1907,8 +1939,8 @@ int init_minigui_printf (int (*output_char) (int ch), #ifdef WIN32 # include -# define isnan _isnan -# define finite _finite +# define isnan _isnan +# define finite _finite #endif #undef _I18N_MB_REQUIRED diff --git a/include/minigui.h b/include/minigui.h index 2b757446..166d8457 100644 --- a/include/minigui.h +++ b/include/minigui.h @@ -3508,6 +3508,327 @@ MG_EXPORT char * strtrimall (char* src); /** @} end of str_helpers */ + /** + * \defgroup slices_allocator_fns Slice Memory Allocator + * + * An efficient way to allocate groups of equal-sized chunks of memory. + * + * Memory slices provide a space-efficient and multi-processing scalable + * way to allocate equal-sized pieces of memory, just like the + * MiniGUI's block data heap (\a block_heap_fns). Relative to the + * standard malloc function and block data heap, this allocator can + * avoid excessive memory-waste, scalability and performance problems. + * + * Note that this implementation is derived from LGPL'd glib. + * + * To achieve these goals, the slice allocator uses a sophisticated, + * layered design that has been inspired by Bonwick's slab allocator + * ([Bonwick94](http://citeseer.ist.psu.edu/bonwick94slab.html) + * Jeff Bonwick, The slab allocator: An object-caching kernel + * memory allocator. USENIX 1994, and + * [Bonwick01](http://citeseer.ist.psu.edu/bonwick01magazines.html) + * Bonwick and Jonathan Adams, Magazines and vmem: Extending the + * slab allocator to many cpu's and arbitrary resources. USENIX 2001) + * + * It uses posix_memalign() to optimize allocations of many equally-sized + * chunks, and has per-thread free lists (the so-called magazine layer) + * to quickly satisfy allocation requests of already known structure sizes. + * This is accompanied by extra caching logic to keep freed memory around + * for some time before returning it to the system. Memory that is unused + * due to alignment constraints is used for cache colorization (random + * distribution of chunk addresses) to improve CPU cache utilization. The + * caching layer of the slice allocator adapts itself to high lock + * contention to improve scalability. + * + * The slice allocator can allocate blocks as small as two pointers, and + * unlike malloc(), it does not reserve extra space per block. For large + * block sizes, mg_slice_new() and mg_slice_alloc() will automatically + * delegate to the system malloc() implementation. For newly written code + * it is recommended to use the new `mg_slice` API instead of malloc() and + * friends, as long as objects are not resized during their lifetime and + * the object size used at allocation time is still available when freeing. + * + * Here is an example for using the slice allocator: + * + * \code + * char *mem[10000]; + * int i; + * + * // Allocate 10000 blocks. + * for (i = 0; i < 10000; i++) + * { + * mem[i] = mg_slice_alloc (50); + * + * // Fill in the memory with some junk. + * for (j = 0; j < 50; j++) + * mem[i][j] = i * j; + * } + * + * // Now free all of the blocks. + * for (i = 0; i < 10000; i++) + * mg_slice_free1 (50, mem[i]); + * \endcode + * + * And here is an example for using the slice allocator + * with data structures: + * + * \code + * MyStruct *array; + * + * // Allocate one block, using the mg_slice_new() macro. + * array = mg_slice_new (MyStruct); + * + * // We can now use array just like a normal pointer to a structure. + * array->data = NULL; + * array->len = 0; + * array->alloc = 0; + * array->zero_terminated = (zero_terminated ? 1 : 0); + * array->clear = (clear ? 1 : 0); + * array->elt_size = elt_size; + * + * // We can free the block, so it can be reused. + * mg_slice_free (MyStruct, array); + * \endcode + * + * @{ + */ + +/** + * mg_slice_alloc: + * @block_size: the number of bytes to allocate + * + * Allocates a block of memory from the slice allocator. + * The block address handed out can be expected to be aligned + * to at least 1 * sizeof (void*), + * though in general slices are 2 * sizeof (void*) bytes aligned, + * if a malloc() fallback implementation is used instead, + * the alignment may be reduced in a libc dependent fashion. + * Note that the underlying slice allocation mechanism can + * be changed with the [`G_SLICE=always-malloc`][G_SLICE] + * environment variable. + * + * Returns: a pointer to the allocated memory block, which will be %NULL if and + * only if @mem_size is 0 + * + * Since: 3.4.0 + */ +MG_EXPORT void *mg_slice_alloc(size_t block_size); + +/** + * mg_slice_alloc0: + * @block_size: the number of bytes to allocate + * + * Allocates a block of memory via mg_slice_alloc() and initializes + * the returned memory to 0. Note that the underlying slice allocation + * mechanism can be changed with the [`G_SLICE=always-malloc`][G_SLICE] + * environment variable. + * + * Returns: a pointer to the allocated block, which will be %NULL if and only + * if @mem_size is 0 + * + * Since: 3.4.0 + */ +MG_EXPORT void *mg_slice_alloc0(size_t block_size); + +/** + * mg_slice_copy: + * @block_size: the number of bytes to allocate + * @mem_block: the memory to copy + * + * Allocates a block of memory from the slice allocator + * and copies @block_size bytes into it from @mem_block. + * + * @mem_block must be non-%NULL if @block_size is non-zero. + * + * Returns: a pointer to the allocated memory block, which will be %NULL if and + * only if @mem_size is 0 + * + * Since: 3.4.0 + */ +MG_EXPORT void *mg_slice_copy(size_t block_size, const void *mem_block); + +/** + * mg_slice_free1: + * @block_size: the size of the block + * @mem_block: a pointer to the block to free + * + * Frees a block of memory. + * + * The memory must have been allocated via mg_slice_alloc() or + * mg_slice_alloc0() and the @block_size has to match the size + * specified upon allocation. Note that the exact release behaviour + * can be changed with the [`G_DEBUG=gc-friendly`][G_DEBUG] environment + * variable, also see [`G_SLICE`][G_SLICE] for related debugging options. + * + * If @mem_block is %NULL, this function does nothing. + * + * Since: 3.4.0 + */ +MG_EXPORT void mg_slice_free1(size_t block_size, void *mem_block); + +/** + * mg_slice_free_chain_with_offset: + * @block_size: the size of the blocks + * @mem_chain: a pointer to the first block of the chain + * @next_offset: the offset of the @next field in the blocks + * + * Frees a linked list of memory blocks of structure type @type. + * + * The memory blocks must be equal-sized, allocated via + * mg_slice_alloc() or mg_slice_alloc0() and linked together by a + * @next pointer (similar to #GSList). The offset of the @next + * field in each block is passed as third argument. + * Note that the exact release behaviour can be changed with the + * [`G_DEBUG=gc-friendly`][G_DEBUG] environment variable, also see + * [`G_SLICE`][G_SLICE] for related debugging options. + * + * If @mem_chain is %NULL, this function does nothing. + * + * Since: 3.4.0 + */ +MG_EXPORT void mg_slice_free_chain_with_offset(size_t block_size, + void *mem_chain, size_t next_offset); + +/** + * mg_slice_new: + * @type: the type to allocate, typically a structure name + * + * A convenience macro to allocate a block of memory from the + * slice allocator. + * + * It calls mg_slice_alloc() with `sizeof (@type)` and casts the + * returned pointer to a pointer of the given type, avoiding a type + * cast in the source code. Note that the underlying slice allocation + * mechanism can be changed with the [`G_SLICE=always-malloc`][G_SLICE] + * environment variable. + * + * This can never return %NULL as the minimum allocation size from + * `sizeof (@type)` is 1 byte. + * + * Returns: (not nullable): a pointer to the allocated block, cast to a pointer + * to @type + * + * Since: 3.4.0 + */ +#define mg_slice_new(type) ((type*)mg_slice_alloc(sizeof (type))) + +/** + * mg_slice_new0: + * @type: the type to allocate, typically a structure name + * + * A convenience macro to allocate a block of memory from the + * slice allocator and set the memory to 0. + * + * It calls mg_slice_alloc0() with `sizeof (@type)` + * and casts the returned pointer to a pointer of the given type, + * avoiding a type cast in the source code. + * Note that the underlying slice allocation mechanism can + * be changed with the [`G_SLICE=always-malloc`][G_SLICE] + * environment variable. + * + * This can never return %NULL as the minimum allocation size from + * `sizeof (@type)` is 1 byte. + * + * Returns: (not nullable): a pointer to the allocated block, cast to a pointer + * to @type + * + * Since: 3.4.0 + */ +#define mg_slice_new0(type) ((type*)mg_slice_alloc0(sizeof (type))) + +/* MemoryBlockType * + * mg_slice_dup (MemoryBlockType, + * MemoryBlockType *mem_block); + * mg_slice_free (MemoryBlockType, + * MemoryBlockType *mem_block); + * mg_slice_free_chain (MemoryBlockType, + * MemoryBlockType *first_chain_block, + * memory_block_next_field); + * pseudo prototypes for the macro definitions following below. + */ + +/** + * mg_slice_dup: + * @type: the type to duplicate, typically a structure name + * @mem: (not nullable): the memory to copy into the allocated block + * + * A convenience macro to duplicate a block of memory using + * the slice allocator. + * + * It calls mg_slice_copy() with `sizeof (@type)` + * and casts the returned pointer to a pointer of the given type, + * avoiding a type cast in the source code. + * Note that the underlying slice allocation mechanism can + * be changed with the [`G_SLICE=always-malloc`][G_SLICE] + * environment variable. + * + * This can never return %NULL. + * + * Returns: (not nullable): a pointer to the allocated block, cast to a pointer + * to @type + * + * Since: 3.4.0 + */ +#define mg_slice_dup(type, mem) \ + (1 ? (type*) mg_slice_copy (sizeof (type), (mem)) \ + : ((void) ((type*) 0 == (mem)), (type*) 0)) + +/** + * mg_slice_free: + * @type: the type of the block to free, typically a structure name + * @mem: a pointer to the block to free + * + * A convenience macro to free a block of memory that has + * been allocated from the slice allocator. + * + * It calls mg_slice_free1() using `sizeof (type)` + * as the block size. + * Note that the exact release behaviour can be changed with the + * [`G_DEBUG=gc-friendly`][G_DEBUG] environment variable, also see + * [`G_SLICE`][G_SLICE] for related debugging options. + * + * If @mem is %NULL, this macro does nothing. + * + * Since: 3.4.0 + */ +#define mg_slice_free(type, mem) \ + do { \ + if (1) mg_slice_free1 (sizeof (type), (mem)); \ + else (void) ((type*) 0 == (mem)); \ + } while(0); + +/** + * mg_slice_free_chain: + * @type: the type of the @mem_chain blocks + * @mem_chain: a pointer to the first block of the chain + * @next: the field name of the next pointer in @type + * + * Frees a linked list of memory blocks of structure type @type. + * The memory blocks must be equal-sized, allocated via + * mg_slice_alloc() or mg_slice_alloc0() and linked together by + * a @next pointer (similar to #GSList). The name of the + * @next field in @type is passed as third argument. + * Note that the exact release behaviour can be changed with the + * [`G_DEBUG=gc-friendly`][G_DEBUG] environment variable, also see + * [`G_SLICE`][G_SLICE] for related debugging options. + * + * If @mem_chain is %NULL, this function does nothing. + * + * Since: 3.4.0 + */ +#define mg_slice_free_chain(type, mem_chain, next) \ + do { \ + if (1) mg_slice_free_chain_with_offset (sizeof (type), \ + (mem_chain), G_STRUCT_OFFSET (type, next)); \ + else (void) ((type*) 0 == (mem_chain)); \ + } while(0); + +#ifdef _MGDEVEL_MODE +MG_EXPORT void mg_slice_debug_tree_statistics(void); +#endif + + /** @} end of slices_allocator_fns */ + /** @} end of global_fns */ /** @} end of fns */ diff --git a/src/include/misc.h b/src/include/misc.h index 248d4f3a..9492ff17 100644 --- a/src/include/misc.h +++ b/src/include/misc.h @@ -74,6 +74,9 @@ void __mg_os_time_delay (int ms); extern GHANDLE hMgEtc; +BOOL mg_InitSliceAllocator(void); +void mg_TerminateSliceAllocator(void); + BOOL mg_InitMgEtc (void); void mg_TerminateMgEtc (void); diff --git a/src/kernel/fixstr.c b/src/kernel/fixstr.c index 7d5238a9..8c69c087 100644 --- a/src/kernel/fixstr.c +++ b/src/kernel/fixstr.c @@ -1,39 +1,39 @@ /* - * This file is part of MiniGUI, a mature cross-platform windowing + * This file is part of MiniGUI, a mature cross-platform windowing * and Graphics User Interface (GUI) support system for embedded systems * and smart IoT devices. - * + * * Copyright (C) 2002~2018, Beijing FMSoft Technologies Co., Ltd. * Copyright (C) 1998~2002, WEI Yongming - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program. If not, see . - * + * * Or, - * + * * As this program is a library, any link to this program must follow * GNU General Public License version 3 (GPLv3). If you cannot accept * GPLv3, you need to be licensed from FMSoft. - * + * * If you have got a commercial license of this program, please use it * under the terms and conditions of the commercial license. - * + * * For more information about the commercial license, please refer to * . */ -/* +/* ** fixstr.c: the Fixed String module for MiniGUI. -** +** ** Current maintainer: Wei Yongming. */ @@ -64,7 +64,7 @@ BOOL mg_InitFixStr (void) { int i, j, offset; BYTE* bitmap; - + // allocate memory. if (!(FixStrHeap.heap [0] = malloc (MAX_LEN_FIXSTR * 8 * NR_HEAP))) return FALSE; @@ -80,12 +80,12 @@ BOOL mg_InitFixStr (void) bitmap[j] |= 0xFF; bitmap += 1<= MAX_LEN_FIXSTR) return (char*)malloc (len + 1); - + // determine which heap will use. i = 0; while (ulen) { @@ -134,7 +134,7 @@ char* GUIAPI FixStrAlloc (int len) // if 2K > len >= 1K, then i = 11; if (i == 1) i = 2; bufflen = 1 << i; - + i = NR_HEAP + 1 - i; // i is the heap index; // if i == 7; then bufflen = 4 @@ -149,7 +149,7 @@ char* GUIAPI FixStrAlloc (int len) heap = FixStrHeap.heap[i]; bitmap = FixStrHeap.bitmap + FixStrHeap.offset[i]; btlen = 1 << i; - + for (i = 0; i < btlen; i++) { for(j = 0; j < 8; j++) { if (*bitmap & (0x80 >> j)) { @@ -165,7 +165,7 @@ char* GUIAPI FixStrAlloc (int len) heap += bufflen; } - + bitmap++; } @@ -182,10 +182,10 @@ void GUIAPI FreeFixStr (char* str) int i; int bufflen; int stroff; - + if (str [0] == '\0') return; - + if (str >= FixStrHeap.heap [NR_HEAP] || str < FixStrHeap.heap [0]) { free (str); return; diff --git a/src/kernel/init-lite.c b/src/kernel/init-lite.c index 23331ced..89a2df21 100644 --- a/src/kernel/init-lite.c +++ b/src/kernel/init-lite.c @@ -104,6 +104,12 @@ int InitGUI (int argc, const char* agr[]) tcgetattr (0, &savedtermio); #endif + step++; + if (!mg_InitSliceAllocator ()) { + fprintf (stderr, "KERNEL>InitGUI: failed to initialize slice allocator!\n"); + return step; + } + if (!mg_InitFixStr ()) { err_message (step, "Can not initialize Fixed String heap!\n"); return step; @@ -158,6 +164,7 @@ void TerminateGUI (int rcByGUI) { mg_TerminateMisc (); mg_TerminateFixStr (); + mg_TerminateSliceAllocator(); } #warning ExitGUISafely? @@ -321,6 +328,12 @@ int InitGUI (int argc, const char* agr[]) __mg_def_proc[1] = PreDefDialogProc; __mg_def_proc[2] = PreDefControlProc; + step++; + if (!mg_InitSliceAllocator ()) { + fprintf (stderr, "KERNEL>InitGUI: failed to initialize slice allocator!\n"); + return step; + } + if (!mg_InitFixStr ()) { err_message (step, "Can not initialize Fixed String heap!\n"); return step; @@ -510,8 +523,6 @@ void TerminateGUI (int rcByGUI) #ifdef _MGHAVE_CURSOR mg_TerminateCursor (); #endif - mg_TerminateMisc (); - mg_TerminateFixStr (); #ifdef _MGRM_PROCESSES if (mgIsServer) @@ -535,6 +546,10 @@ void TerminateGUI (int rcByGUI) client_ClientCleanup (); } #endif + + mg_TerminateMisc (); + mg_TerminateFixStr (); + mg_TerminateSliceAllocator(); } #endif /* ifdef _MG_MINIMALGDI */ diff --git a/src/kernel/init.c b/src/kernel/init.c index e6f7f844..f3641de4 100644 --- a/src/kernel/init.c +++ b/src/kernel/init.c @@ -376,12 +376,18 @@ int GUIAPI InitGUI (int args, const char *agr[]) __mg_def_proc[1] = PreDefDialogProc; __mg_def_proc[2] = PreDefControlProc; + step++; + if (!mg_InitSliceAllocator ()) { + fprintf (stderr, "KERNEL>InitGUI: failed to initialize slice allocator!\n"); + return step; + } + step++; if (!mg_InitFixStr ()) { fprintf (stderr, "KERNEL>InitGUI: Init Fixed String module failure!\n"); return step; } - + step++; /* Init miscelleous*/ if (!mg_InitMisc ()) { @@ -586,6 +592,8 @@ void GUIAPI TerminateGUI (int not_used) mg_miFreeArcCache (); #endif + mg_TerminateSliceAllocator(); + /* * Restore original termio *tcsetattr (0, TCSAFLUSH, &savedtermio); diff --git a/src/libc/Makefile.am b/src/libc/Makefile.am index 94932c05..29551ea2 100644 --- a/src/libc/Makefile.am +++ b/src/libc/Makefile.am @@ -2,7 +2,7 @@ AM_CPPFLAGS = -I$(abs_top_srcdir)/include -I$(abs_top_srcdir)/src/include -I$(ab noinst_LTLIBRARIES = liblibc.la -SRC_FILES=malloc.c \ +SRC_FILES=malloc.c mgslice.c \ defdev.c stdioinlines.c \ fnprintf.c fprintf.c printf.c vfnprintf.c \ snprintf.c sprintf.c vsnprintf.c vfscanf.c \ @@ -12,12 +12,12 @@ SRC_FILES=malloc.c \ threadx_pprivate.c threadx_pthread.c threadx_mutex.c threadx_sem.c \ nucleus_pthread.c nucleus_mutex.c nucleus_sem.c \ vxworks_pthread.c vxworks_mutex.c vxworks_sem.c \ - ose_sem.c \ + ose_sem.c \ psos_pprivate.c psos_pthread.c psos_mutex.c psos_sem.c HDR_FILES=ucos2_pprivate.h sysvipc_private.h ieeefp.h \ - threadx_pprivate.h nucleus_pprivate.h vxworks_pprivate.h \ - psos_pprivate.h + threadx_pprivate.h nucleus_pprivate.h vxworks_pprivate.h \ + psos_pprivate.h EXTRA_DIST=makefile.ng makefile.msvc vxworks_sem.c diff --git a/src/libc/mgslice.c b/src/libc/mgslice.c new file mode 100644 index 00000000..925b9cc0 --- /dev/null +++ b/src/libc/mgslice.c @@ -0,0 +1,1432 @@ +/* + * This file is part of MiniGUI, a mature cross-platform windowing + * and Graphics User Interface (GUI) support system for embedded systems + * and smart IoT devices. + * + * Copyright (C) 2019, Beijing FMSoft Technologies Co., Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Or, + * + * As this program is a library, any link to this program must follow + * GNU General Public License version 3 (GPLv3). If you cannot accept + * GPLv3, you need to be licensed from FMSoft. + * + * If you have got a commercial license of this program, please use it + * under the terms and conditions of the commercial license. + * + * For more information about the commercial license, please refer to + * . + */ + +/* +** sliced memory - a fast concurrent memory chunk allocator +** +** Create by WEI Yongming at 2019/04/04 +** +** This implementation is derived from LGPL'd GLib: +** +** Copyright (C) 2005 Tim Janik +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; if not, see . +*/ + +#include "common.h" +#include "minigui.h" + +#if defined(HAVE_POSIX_MEMALIGN) && !defined(_XOPEN_SOURCE) +#define _XOPEN_SOURCE 600 /* posix_memalign() */ +#endif +#include /* posix_memalign() */ +#include +#include + +#ifndef __NOUNIX__ +#include /* sysconf() */ +#include +#endif + +#ifdef WIN32 +#include +#include +#endif + +#include /* fputs */ + +/* the slice allocator is split up into 4 layers, roughly modelled after the slab + * allocator and magazine extensions as outlined in: + * + [Bonwick94] Jeff Bonwick, The slab allocator: An object-caching kernel + * memory allocator. USENIX 1994, http://citeseer.ist.psu.edu/bonwick94slab.html + * + [Bonwick01] Bonwick and Jonathan Adams, Magazines and vmem: Extending the + * slab allocator to many cpu's and arbitrary resources. + * USENIX 2001, http://citeseer.ist.psu.edu/bonwick01magazines.html + * the layers are: + * - the thread magazines. for each (aligned) chunk size, a magazine (a list) + * of recently freed and soon to be allocated chunks is maintained per thread. + * this way, most alloc/free requests can be quickly satisfied from per-thread + * free lists which only require one pthread_getspecific() call to retrive the + * thread handle. + * - the magazine cache. allocating and freeing chunks to/from threads only + * occours at magazine sizes from a global depot of magazines. the depot + * maintaines a 15 second working set of allocated magazines, so full + * magazines are not allocated and released too often. + * the chunk size dependent magazine sizes automatically adapt (within limits, + * see [3]) to lock contention to properly scale performance across a variety + * of SMP systems. + * - the slab allocator. this allocator allocates slabs (blocks of memory) close + * to the system page size or multiples thereof which have to be page aligned. + * the blocks are divided into smaller chunks which are used to satisfy + * allocations from the upper layers. the space provided by the reminder of + * the chunk size division is used for cache colorization (random distribution + * of chunk addresses) to improve processor cache utilization. multiple slabs + * with the same chunk size are kept in a partially sorted ring to allow O(1) + * freeing and allocation of chunks (as long as the allocation of an entirely + * new slab can be avoided). + * - the page allocator. on most modern systems, posix_memalign(3) or + * memalign(3) should be available, so this is used to allocate blocks with + * system page size based alignments and sizes or multiples thereof. + * if no memalign variant is provided, valloc() is used instead and + * block sizes are limited to the system page size (no multiples thereof). + * as a fallback, on system without even valloc(), a malloc(3)-based page + * allocator with alloc-only behaviour is used. + * + * NOTES: + * [1] some systems memalign(3) implementations may rely on boundary tagging for + * the handed out memory chunks. to avoid excessive page-wise fragmentation, + * we reserve 2 * sizeof (void*) per block size for the systems memalign(3), + * specified in NATIVE_MALLOC_PADDING. + * [2] using the slab allocator alone already provides for a fast and efficient + * allocator, it doesn't properly scale beyond single-threaded uses though. + * also, the slab allocator implements eager free(3)-ing, i.e. does not + * provide any form of caching or working set maintenance. so if used alone, + * it's vulnerable to trashing for sequences of balanced (alloc, free) pairs + * at certain thresholds. + * [3] magazine sizes are bound by an implementation specific minimum size and + * a chunk size specific maximum to limit magazine storage sizes to roughly + * 16KB. + * [4] allocating ca. 8 chunks per block/page keeps a good balance between + * external and internal fragmentation (<= 12.5%). [Bonwick94] + */ + +/* --- macros and constants --- */ +#define LARGEALIGNMENT (256) +#define P2ALIGNMENT (2 * sizeof (size_t)) /* fits 2 pointers (assumed to be 2 * GLIB_SIZEOF_SIZE_T below) */ +#define ALIGN(size, base) ((base) * (size_t) (((size) + (base) - 1) / (base))) +#define NATIVE_MALLOC_PADDING P2ALIGNMENT /* per-page padding left for native malloc(3) see [1] */ +#define SLAB_INFO_SIZE P2ALIGN (sizeof (SlabInfo) + NATIVE_MALLOC_PADDING) +#define MAX_MAGAZINE_SIZE (256) /* see [3] and allocator_get_magazine_threshold() for this */ +#define MIN_MAGAZINE_SIZE (4) +#define MAX_STAMP_COUNTER (7) /* distributes the load of gettimeofday() */ +#define MAX_SLAB_CHUNK_SIZE(al) (((al)->max_page_size - SLAB_INFO_SIZE) / 8) /* we want at last 8 chunks per page, see [4] */ +#define MAX_SLAB_INDEX(al) (SLAB_INDEX (al, MAX_SLAB_CHUNK_SIZE (al)) + 1) +#define SLAB_INDEX(al, asize) ((asize) / P2ALIGNMENT - 1) /* asize must be P2ALIGNMENT aligned */ +#define SLAB_CHUNK_SIZE(al, ix) (((ix) + 1) * P2ALIGNMENT) +#define SLAB_BPAGE_SIZE(al,csz) (8 * (csz) + SLAB_INFO_SIZE) + +/* optimized version of ALIGN (size, P2ALIGNMENT) */ +#if GLIB_SIZEOF_SIZE_T * 2 == 8 /* P2ALIGNMENT */ +#define P2ALIGN(size) (((size) + 0x7) & ~(size_t) 0x7) +#elif GLIB_SIZEOF_SIZE_T * 2 == 16 /* P2ALIGNMENT */ +#define P2ALIGN(size) (((size) + 0xf) & ~(size_t) 0xf) +#else +#define P2ALIGN(size) ALIGN (size, P2ALIGNMENT) +#endif + +/* special helpers to avoid gmessage.c dependency */ +static void mem_error (const char *format, ...); +#define mem_assert(cond) \ + do { \ + if (MG_LIKELY (cond)) \ + ; \ + else \ + mem_error ("assertion failed: %s", #cond); \ + } while (0) + +/* --- structures --- */ +typedef struct _ChunkLink ChunkLink; +typedef struct _SlabInfo SlabInfo; +typedef struct _CachedMagazine CachedMagazine; +struct _ChunkLink { + ChunkLink *next; + ChunkLink *data; +}; +struct _SlabInfo { + ChunkLink *chunks; + unsigned int n_allocated; + SlabInfo *next, *prev; +}; +typedef struct { + ChunkLink *chunks; + size_t count; /* approximative chunks list length */ +} Magazine; + +typedef struct { + Magazine *magazine1; /* array of MAX_SLAB_INDEX (allocator) */ + Magazine *magazine2; /* array of MAX_SLAB_INDEX (allocator) */ +} ThreadMemory; + +typedef struct { + BOOL always_malloc; + BOOL bypass_magazines; + BOOL debug_blocks; + size_t working_set_msecs; + unsigned int color_increment; +} SliceConfig; + +typedef struct { + /* const after initialization */ + size_t min_page_size, max_page_size; + SliceConfig config; + size_t max_slab_chunk_size_for_magazine_cache; + /* magazine cache */ + pthread_mutex_t magazine_mutex; + ChunkLink **magazines; /* array of MAX_SLAB_INDEX (allocator) */ + unsigned int *contention_counters; /* array of MAX_SLAB_INDEX (allocator) */ + int mutex_counter; + uintptr_t stamp_counter; + uintptr_t last_stamp; + /* slab allocator */ + pthread_mutex_t slab_mutex; + SlabInfo **slab_stack; /* array of MAX_SLAB_INDEX (allocator) */ + unsigned int color_accu; +} Allocator; + +/* --- g-slice prototypes --- */ +static void *slab_allocator_alloc_chunk(size_t chunk_size); +static void slab_allocator_free_chunk(size_t chunk_size, void *mem); +static void private_thread_memory_cleanup(void *data); +static void *allocator_memalign(size_t alignment, size_t memsize); +static void allocator_memfree(size_t memsize, void *mem); +static inline void magazine_cache_update_stamp (void); +static inline size_t allocator_get_magazine_threshold(Allocator *allocator, unsigned int ix); + +/* --- g-slice memory checker --- */ +#ifdef _MGDEVEL_MODE +static void smc_notify_alloc(void *pointer, size_t size); +static int smc_notify_free(void *pointer, size_t size); +#else +static inline void smc_notify_alloc (void *pointer, size_t size) +{ + return; +} + +static inline int smc_notify_free (void *pointer, size_t size) +{ + return 1; +} +#endif + +static pthread_key_t private_thread_memory; + +BOOL mg_InitSliceAllocator(void) +{ + return pthread_key_create(&private_thread_memory, + private_thread_memory_cleanup) == 0; +} + +void mg_TerminateSliceAllocator(void) +{ +} + +static size_t sys_page_size = 0; +static Allocator allocator[1] = { { 0, }, }; +static SliceConfig slice_config = { + FALSE, /* always_malloc */ + FALSE, /* bypass_magazines */ + FALSE, /* debug_blocks */ + 15 * 1000, /* working_set_msecs */ + 1, /* color increment, alt: 0x7fffffff */ +}; + +static void slice_config_init (SliceConfig *config) +{ + const char *val; + + *config = slice_config; + + val = getenv ("MG_SLICE"); + if (val != NULL) { + if (strstr(val, "always-malloc")) + config->always_malloc = TRUE; +#ifdef _MGDEVEL_MODE + if (strstr(val, "debug-blocks")) + config->debug_blocks = TRUE; +#endif + } +} + +#define mg_new0(type, n) calloc(n, sizeof(type)) + +static void mg_slice_init_nomessage (void) +{ + /* we may not use g_error() or friends here */ + mem_assert (sys_page_size == 0); + mem_assert (MIN_MAGAZINE_SIZE >= 4); + +#ifdef WIN32 + { + SYSTEM_INFO system_info; + GetSystemInfo (&system_info); + sys_page_size = system_info.dwPageSize; + } +#else + sys_page_size = sysconf (_SC_PAGESIZE); /* = sysconf (_SC_PAGE_SIZE); = getpagesize(); */ +#endif + mem_assert (sys_page_size >= 2 * LARGEALIGNMENT); + mem_assert ((sys_page_size & (sys_page_size - 1)) == 0); + slice_config_init (&allocator->config); + allocator->min_page_size = sys_page_size; +#if HAVE_POSIX_MEMALIGN || HAVE_MEMALIGN + /* allow allocation of pages up to 8KB (with 8KB alignment). + * this is useful because many medium to large sized structures + * fit less than 8 times (see [4]) into 4KB pages. + * we allow very small page sizes here, to reduce wastage in + * threads if only small allocations are required (this does + * bear the risk of increasing allocation times and fragmentation + * though). + */ + allocator->min_page_size = MAX (allocator->min_page_size, 4096); + allocator->max_page_size = MAX (allocator->min_page_size, 8192); + allocator->min_page_size = MIN (allocator->min_page_size, 128); +#else + /* we can only align to system page size */ + allocator->max_page_size = sys_page_size; +#endif + if (allocator->config.always_malloc) + { + allocator->contention_counters = NULL; + allocator->magazines = NULL; + allocator->slab_stack = NULL; + } + else + { + allocator->contention_counters = mg_new0 (unsigned int, MAX_SLAB_INDEX (allocator)); + allocator->magazines = mg_new0 (ChunkLink*, MAX_SLAB_INDEX (allocator)); + allocator->slab_stack = mg_new0 (SlabInfo*, MAX_SLAB_INDEX (allocator)); + } + + allocator->mutex_counter = 0; + allocator->stamp_counter = MAX_STAMP_COUNTER; /* force initial update */ + allocator->last_stamp = 0; + allocator->color_accu = 0; + magazine_cache_update_stamp(); + /* values cached for performance reasons */ + allocator->max_slab_chunk_size_for_magazine_cache = MAX_SLAB_CHUNK_SIZE (allocator); + if (allocator->config.always_malloc || allocator->config.bypass_magazines) + allocator->max_slab_chunk_size_for_magazine_cache = 0; /* non-optimized cases */ +} + +static inline unsigned int allocator_categorize (size_t aligned_chunk_size) +{ + /* speed up the likely path */ + if (MG_LIKELY (aligned_chunk_size && aligned_chunk_size <= allocator->max_slab_chunk_size_for_magazine_cache)) + return 1; /* use magazine cache */ + + if (!allocator->config.always_malloc && + aligned_chunk_size && + aligned_chunk_size <= MAX_SLAB_CHUNK_SIZE (allocator)) + { + if (allocator->config.bypass_magazines) + return 2; /* use slab allocator, see [2] */ + return 1; /* use magazine cache */ + } + return 0; /* use malloc() */ +} + +static inline void g_mutex_lock_a (pthread_mutex_t *mutex, + unsigned int *contention_counter) +{ + BOOL contention = FALSE; + if (!pthread_mutex_trylock(mutex)) + { + pthread_mutex_lock (mutex); + contention = TRUE; + } + if (contention) + { + allocator->mutex_counter++; + if (allocator->mutex_counter >= 1) /* quickly adapt to contention */ + { + allocator->mutex_counter = 0; + *contention_counter = MIN (*contention_counter + 1, MAX_MAGAZINE_SIZE); + } + } + else /* !contention */ + { + allocator->mutex_counter--; + if (allocator->mutex_counter < -11) /* moderately recover magazine sizes */ + { + allocator->mutex_counter = 0; + *contention_counter = MAX (*contention_counter, 1) - 1; + } + } +} + +static inline void *_steal_pointer (void *pp) +{ + void **ptr = (void **) pp; + void * ref; + + ref = *ptr; + *ptr = NULL; + + return ref; +} + +static void* _private_set_alloc0 (pthread_key_t key, size_t size) +{ + void* allocated = calloc (1, size); + + pthread_setspecific (key, allocated); + + return _steal_pointer (&allocated); +} + +static inline ThreadMemory* thread_memory_from_self (void) +{ + ThreadMemory *tmem = pthread_getspecific (private_thread_memory); + if (MG_UNLIKELY (!tmem)) + { + static pthread_mutex_t init_mutex; + unsigned int n_magazines; + + pthread_mutex_lock (&init_mutex); + if MG_UNLIKELY (sys_page_size == 0) + mg_slice_init_nomessage (); + pthread_mutex_unlock (&init_mutex); + + n_magazines = MAX_SLAB_INDEX (allocator); + tmem = _private_set_alloc0 (private_thread_memory, + sizeof (ThreadMemory) + sizeof (Magazine) * 2 * n_magazines); + tmem->magazine1 = (Magazine*) (tmem + 1); + tmem->magazine2 = &tmem->magazine1[n_magazines]; + } + return tmem; +} + +static inline ChunkLink* magazine_chain_pop_head(ChunkLink **magazine_chunks) +{ + /* magazine chains are linked via ChunkLink->next. + * each ChunkLink->data of the toplevel chain may point to a subchain, + * linked via ChunkLink->next. ChunkLink->data of the subchains just + * contains uninitialized junk. + */ + ChunkLink *chunk = (*magazine_chunks)->data; + if (MG_UNLIKELY (chunk)) + { + /* allocating from freed list */ + (*magazine_chunks)->data = chunk->next; + } + else + { + chunk = *magazine_chunks; + *magazine_chunks = chunk->next; + } + return chunk; +} + +static inline size_t allocator_get_magazine_threshold (Allocator *allocator, + unsigned int ix) +{ + /* the magazine size calculated here has a lower bound of MIN_MAGAZINE_SIZE, + * which is required by the implementation. also, for moderately sized chunks + * (say >= 64 bytes), magazine sizes shouldn't be much smaller then the number + * of chunks available per page/2 to avoid excessive traffic in the magazine + * cache for small to medium sized structures. + * the upper bound of the magazine size is effectively provided by + * MAX_MAGAZINE_SIZE. for larger chunks, this number is scaled down so that + * the content of a single magazine doesn't exceed ca. 16KB. + */ + size_t chunk_size = SLAB_CHUNK_SIZE (allocator, ix); + unsigned int threshold = MAX (MIN_MAGAZINE_SIZE, allocator->max_page_size / MAX (5 * chunk_size, 5 * 32)); + unsigned int contention_counter = allocator->contention_counters[ix]; + if (MG_UNLIKELY (contention_counter)) /* single CPU bias */ + { + /* adapt contention counter thresholds to chunk sizes */ + contention_counter = contention_counter * 64 / chunk_size; + threshold = MAX (threshold, contention_counter); + } + return threshold; +} + +/* --- magazine cache --- */ +static inline void magazine_cache_update_stamp (void) +{ + if (allocator->stamp_counter >= MAX_STAMP_COUNTER) { + struct timeval tv; + gettimeofday (&tv, NULL); + /* milli seconds */ + allocator->last_stamp = tv.tv_sec * 1000 + tv.tv_usec / 1000; + allocator->stamp_counter = 0; + } + else + allocator->stamp_counter++; +} + +static inline ChunkLink* magazine_chain_prepare_fields ( + ChunkLink *magazine_chunks) +{ + ChunkLink *chunk1; + ChunkLink *chunk2; + ChunkLink *chunk3; + ChunkLink *chunk4; + /* checked upon initialization: mem_assert (MIN_MAGAZINE_SIZE >= 4); */ + /* ensure a magazine with at least 4 unused data pointers */ + chunk1 = magazine_chain_pop_head (&magazine_chunks); + chunk2 = magazine_chain_pop_head (&magazine_chunks); + chunk3 = magazine_chain_pop_head (&magazine_chunks); + chunk4 = magazine_chain_pop_head (&magazine_chunks); + chunk4->next = magazine_chunks; + chunk3->next = chunk4; + chunk2->next = chunk3; + chunk1->next = chunk2; + return chunk1; +} + +/* access the first 3 fields of a specially prepared magazine chain */ +#define magazine_chain_prev(mc) ((mc)->data) +#define magazine_chain_stamp(mc) ((mc)->next->data) +#define magazine_chain_uint_stamp(mc) (uintptr_t)((mc)->next->data) +#define magazine_chain_next(mc) ((mc)->next->next->data) +#define magazine_chain_count(mc) ((mc)->next->next->next->data) + +static void magazine_cache_trim (Allocator *allocator, + unsigned int ix, unsigned int stamp) +{ + /* pthread_mutex_lock (allocator->mutex); done by caller */ + /* trim magazine cache from tail */ + ChunkLink *current = magazine_chain_prev (allocator->magazines[ix]); + ChunkLink *trash = NULL; + while (ABS (stamp - magazine_chain_uint_stamp (current)) >= + allocator->config.working_set_msecs) + { + /* unlink */ + ChunkLink *prev = magazine_chain_prev (current); + ChunkLink *next = magazine_chain_next (current); + magazine_chain_next (prev) = next; + magazine_chain_prev (next) = prev; + /* clear special fields, put on trash stack */ + magazine_chain_next (current) = NULL; + magazine_chain_count (current) = NULL; + magazine_chain_stamp (current) = NULL; + magazine_chain_prev (current) = trash; + trash = current; + /* fixup list head if required */ + if (current == allocator->magazines[ix]) + { + allocator->magazines[ix] = NULL; + break; + } + current = prev; + } + pthread_mutex_unlock (&allocator->magazine_mutex); + /* free trash */ + if (trash) + { + const size_t chunk_size = SLAB_CHUNK_SIZE (allocator, ix); + pthread_mutex_lock (&allocator->slab_mutex); + while (trash) + { + current = trash; + trash = magazine_chain_prev (current); + magazine_chain_prev (current) = NULL; /* clear special field */ + while (current) + { + ChunkLink *chunk = magazine_chain_pop_head (¤t); + slab_allocator_free_chunk (chunk_size, chunk); + } + } + pthread_mutex_unlock (&allocator->slab_mutex); + } +} + +/* count must be >= MIN_MAGAZINE_SIZE */ +static void magazine_cache_push_magazine (unsigned int ix, + ChunkLink *magazine_chunks, size_t count) +{ + ChunkLink *current = magazine_chain_prepare_fields (magazine_chunks); + ChunkLink *next, *prev; + pthread_mutex_lock (&allocator->magazine_mutex); + /* add magazine at head */ + next = allocator->magazines[ix]; + if (next) + prev = magazine_chain_prev (next); + else + next = prev = current; + magazine_chain_next (prev) = current; + magazine_chain_prev (next) = current; + magazine_chain_prev (current) = prev; + magazine_chain_next (current) = next; + magazine_chain_count (current) = (void *)count; + /* stamp magazine */ + magazine_cache_update_stamp(); + magazine_chain_stamp (current) = (void *)(allocator->last_stamp); + allocator->magazines[ix] = current; + /* free old magazines beyond a certain threshold */ + magazine_cache_trim (allocator, ix, allocator->last_stamp); + /* pthread_mutex_unlock (allocator->mutex); was done by magazine_cache_trim() */ +} + +static ChunkLink* magazine_cache_pop_magazine (unsigned int ix, + size_t *countp) +{ + g_mutex_lock_a (&allocator->magazine_mutex, + &allocator->contention_counters[ix]); + + if (!allocator->magazines[ix]) { + unsigned int magazine_threshold = allocator_get_magazine_threshold (allocator, ix); + size_t i, chunk_size = SLAB_CHUNK_SIZE (allocator, ix); + ChunkLink *chunk, *head; + pthread_mutex_unlock (&allocator->magazine_mutex); + pthread_mutex_lock (&allocator->slab_mutex); + head = slab_allocator_alloc_chunk (chunk_size); + head->data = NULL; + chunk = head; + for (i = 1; i < magazine_threshold; i++) + { + chunk->next = slab_allocator_alloc_chunk (chunk_size); + chunk = chunk->next; + chunk->data = NULL; + } + chunk->next = NULL; + pthread_mutex_unlock (&allocator->slab_mutex); + *countp = i; + return head; + } + else { + ChunkLink *current = allocator->magazines[ix]; + ChunkLink *prev = magazine_chain_prev (current); + ChunkLink *next = magazine_chain_next (current); + /* unlink */ + magazine_chain_next (prev) = next; + magazine_chain_prev (next) = prev; + allocator->magazines[ix] = next == current ? NULL : next; + pthread_mutex_unlock (&allocator->magazine_mutex); + /* clear special fields and hand out */ + *countp = (size_t) magazine_chain_count (current); + magazine_chain_prev (current) = NULL; + magazine_chain_next (current) = NULL; + magazine_chain_count (current) = NULL; + magazine_chain_stamp (current) = NULL; + return current; + } +} + +/* --- thread magazines --- */ +static void private_thread_memory_cleanup (void * data) +{ + ThreadMemory *tmem = data; + const unsigned int n_magazines = MAX_SLAB_INDEX (allocator); + unsigned int ix; + for (ix = 0; ix < n_magazines; ix++) + { + Magazine *mags[2]; + unsigned int j; + mags[0] = &tmem->magazine1[ix]; + mags[1] = &tmem->magazine2[ix]; + for (j = 0; j < 2; j++) + { + Magazine *mag = mags[j]; + if (mag->count >= MIN_MAGAZINE_SIZE) + magazine_cache_push_magazine (ix, mag->chunks, mag->count); + else + { + const size_t chunk_size = SLAB_CHUNK_SIZE (allocator, ix); + pthread_mutex_lock (&allocator->slab_mutex); + while (mag->chunks) + { + ChunkLink *chunk = magazine_chain_pop_head (&mag->chunks); + slab_allocator_free_chunk (chunk_size, chunk); + } + pthread_mutex_unlock (&allocator->slab_mutex); + } + } + } + free (tmem); +} + +static void thread_memory_magazine1_reload (ThreadMemory *tmem, + unsigned int ix) +{ + Magazine *mag = &tmem->magazine1[ix]; + mem_assert (mag->chunks == NULL); /* ensure that we may reset mag->count */ + mag->count = 0; + mag->chunks = magazine_cache_pop_magazine (ix, &mag->count); +} + +static void thread_memory_magazine2_unload (ThreadMemory *tmem, + unsigned int ix) +{ + Magazine *mag = &tmem->magazine2[ix]; + magazine_cache_push_magazine (ix, mag->chunks, mag->count); + mag->chunks = NULL; + mag->count = 0; +} + +static inline void thread_memory_swap_magazines (ThreadMemory *tmem, + unsigned int ix) +{ + Magazine xmag = tmem->magazine1[ix]; + tmem->magazine1[ix] = tmem->magazine2[ix]; + tmem->magazine2[ix] = xmag; +} + +static inline BOOL thread_memory_magazine1_is_empty (ThreadMemory *tmem, + unsigned int ix) +{ + return tmem->magazine1[ix].chunks == NULL; +} + +static inline BOOL thread_memory_magazine2_is_full (ThreadMemory *tmem, + unsigned int ix) +{ + return tmem->magazine2[ix].count >= + allocator_get_magazine_threshold (allocator, ix); +} + +static inline void * thread_memory_magazine1_alloc (ThreadMemory *tmem, + unsigned int ix) +{ + Magazine *mag = &tmem->magazine1[ix]; + ChunkLink *chunk = magazine_chain_pop_head (&mag->chunks); + if (MG_LIKELY (mag->count > 0)) + mag->count--; + return chunk; +} + +static inline void +thread_memory_magazine2_free (ThreadMemory *tmem, + unsigned int ix, void *mem) +{ + Magazine *mag = &tmem->magazine2[ix]; + ChunkLink *chunk = mem; + chunk->data = NULL; + chunk->next = mag->chunks; + mag->chunks = chunk; + mag->count++; +} + +/* --- API functions --- */ +void *mg_slice_alloc (size_t mem_size) +{ + ThreadMemory *tmem; + size_t chunk_size; + void * mem; + unsigned int acat; + + /* This gets the private structure for this thread. If the private + * structure does not yet exist, it is created. + * + * This has a side effect of causing GSlice to be initialised, so it + * must come first. + */ + tmem = thread_memory_from_self (); + + chunk_size = P2ALIGN (mem_size); + acat = allocator_categorize (chunk_size); + if (MG_LIKELY (acat == 1)) /* allocate through magazine layer */ + { + unsigned int ix = SLAB_INDEX (allocator, chunk_size); + if (MG_UNLIKELY (thread_memory_magazine1_is_empty (tmem, ix))) + { + thread_memory_swap_magazines (tmem, ix); + if (MG_UNLIKELY (thread_memory_magazine1_is_empty (tmem, ix))) + thread_memory_magazine1_reload (tmem, ix); + } + mem = thread_memory_magazine1_alloc (tmem, ix); + } + else if (acat == 2) /* allocate through slab allocator */ + { + pthread_mutex_lock (&allocator->slab_mutex); + mem = slab_allocator_alloc_chunk (chunk_size); + pthread_mutex_unlock (&allocator->slab_mutex); + } + else /* delegate to system malloc */ + mem = malloc (mem_size); + if (MG_UNLIKELY (allocator->config.debug_blocks)) + smc_notify_alloc (mem, mem_size); + + _DBG_PRINTF("%s: slice allocated: %p (%lu)\n", + __FUNCTION__, (void*)mem, mem_size); + + return mem; +} + +void * mg_slice_alloc0 (size_t mem_size) +{ + void * mem = mg_slice_alloc (mem_size); + if (mem) + memset (mem, 0, mem_size); + return mem; +} + +void *mg_slice_copy (size_t mem_size, const void *mem_block) +{ + void *mem = mg_slice_alloc (mem_size); + if (mem) + memcpy (mem, mem_block, mem_size); + return mem; +} + +BOOL __mg_mem_gc_friendly; + +void mg_slice_free1 (size_t mem_size, void *mem_block) +{ + size_t chunk_size = P2ALIGN (mem_size); + unsigned int acat = allocator_categorize (chunk_size); + if (MG_UNLIKELY (!mem_block)) + return; + if (MG_UNLIKELY (allocator->config.debug_blocks) && + !smc_notify_free (mem_block, mem_size)) + abort(); + if (MG_LIKELY (acat == 1)) /* allocate through magazine layer */ + { + ThreadMemory *tmem = thread_memory_from_self(); + unsigned int ix = SLAB_INDEX (allocator, chunk_size); + if (MG_UNLIKELY (thread_memory_magazine2_is_full (tmem, ix))) + { + thread_memory_swap_magazines (tmem, ix); + if (MG_UNLIKELY (thread_memory_magazine2_is_full (tmem, ix))) + thread_memory_magazine2_unload (tmem, ix); + } + if (MG_UNLIKELY (__mg_mem_gc_friendly)) + memset (mem_block, 0, chunk_size); + thread_memory_magazine2_free (tmem, ix, mem_block); + } + else if (acat == 2) /* allocate through slab allocator */ + { + if (MG_UNLIKELY (__mg_mem_gc_friendly)) + memset (mem_block, 0, chunk_size); + pthread_mutex_lock (&allocator->slab_mutex); + slab_allocator_free_chunk (chunk_size, mem_block); + pthread_mutex_unlock (&allocator->slab_mutex); + } + else /* delegate to system malloc */ + { + if (MG_UNLIKELY (__mg_mem_gc_friendly)) + memset (mem_block, 0, mem_size); + free (mem_block); + } + + _DBG_PRINTF("%s: slice freed: %p (%lu)\n", + __FUNCTION__, (void*)mem_block, mem_size); +} + +void mg_slice_free_chain_with_offset (size_t mem_size, + void * mem_chain, size_t next_offset) +{ + void * slice = mem_chain; + /* while the thread magazines and the magazine cache are implemented so that + * they can easily be extended to allow for free lists containing more free + * lists for the first level nodes, which would allow O(1) freeing in this + * function, the benefit of such an extension is questionable, because: + * - the magazine size counts will become mere lower bounds which confuses + * the code adapting to lock contention; + * - freeing a single node to the thread magazines is very fast, so this + * O(list_length) operation is multiplied by a fairly small factor; + * - memory usage histograms on larger applications seem to indicate that + * the amount of released multi node lists is negligible in comparison + * to single node releases. + * - the major performance bottle neck, namely pthread_getspecific() or + * pthread_mutex_lock()/pthread_mutex_unlock() has already been moved out of the + * inner loop for freeing chained slices. + */ + size_t chunk_size = P2ALIGN (mem_size); + unsigned int acat = allocator_categorize (chunk_size); + if (MG_LIKELY (acat == 1)) /* allocate through magazine layer */ + { + ThreadMemory *tmem = thread_memory_from_self(); + unsigned int ix = SLAB_INDEX (allocator, chunk_size); + while (slice) + { + Uint8 *current = slice; + slice = *(void **) (current + next_offset); + if (MG_UNLIKELY (allocator->config.debug_blocks) && + !smc_notify_free (current, mem_size)) + abort(); + if (MG_UNLIKELY (thread_memory_magazine2_is_full (tmem, ix))) + { + thread_memory_swap_magazines (tmem, ix); + if (MG_UNLIKELY (thread_memory_magazine2_is_full (tmem, ix))) + thread_memory_magazine2_unload (tmem, ix); + } + if (MG_UNLIKELY (__mg_mem_gc_friendly)) + memset (current, 0, chunk_size); + thread_memory_magazine2_free (tmem, ix, current); + } + } + else if (acat == 2) /* allocate through slab allocator */ + { + pthread_mutex_lock (&allocator->slab_mutex); + while (slice) + { + Uint8 *current = slice; + slice = *(void **) (current + next_offset); + if (MG_UNLIKELY (allocator->config.debug_blocks) && + !smc_notify_free (current, mem_size)) + abort(); + if (MG_UNLIKELY (__mg_mem_gc_friendly)) + memset (current, 0, chunk_size); + slab_allocator_free_chunk (chunk_size, current); + } + pthread_mutex_unlock (&allocator->slab_mutex); + } + else /* delegate to system malloc */ + while (slice) + { + Uint8 *current = slice; + slice = *(void **) (current + next_offset); + if (MG_UNLIKELY (allocator->config.debug_blocks) && + !smc_notify_free (current, mem_size)) + abort(); + if (MG_UNLIKELY (__mg_mem_gc_friendly)) + memset (current, 0, mem_size); + free (current); + } +} + +/* --- single page allocator --- */ +static void allocator_slab_stack_push(Allocator *allocator, + unsigned int ix, SlabInfo *sinfo) +{ + /* insert slab at slab ring head */ + if (!allocator->slab_stack[ix]) + { + sinfo->next = sinfo; + sinfo->prev = sinfo; + } + else + { + SlabInfo *next = allocator->slab_stack[ix], *prev = next->prev; + next->prev = sinfo; + prev->next = sinfo; + sinfo->next = next; + sinfo->prev = prev; + } + allocator->slab_stack[ix] = sinfo; +} + +static inline unsigned int g_bit_storage (unsigned long number) +{ +#if defined(__GNUC__) && (__GNUC__ >= 4) && defined(__OPTIMIZE__) + return MG_LIKELY (number) ? + ((sizeof(unsigned long) * 8U - 1) ^ (unsigned int) __builtin_clzl(number)) + 1 : 1; +#else + unsigned int n_bits = 0; + + do { + n_bits++; + number >>= 1; + } while (number); + + return n_bits; +#endif +} + +static size_t allocator_aligned_page_size (Allocator *allocator, + size_t n_bytes) +{ + size_t val = 1 << g_bit_storage (n_bytes - 1); + val = MAX (val, allocator->min_page_size); + return val; +} + +static void allocator_add_slab (Allocator *allocator, + unsigned int ix, size_t chunk_size) +{ + ChunkLink *chunk; + SlabInfo *sinfo; + size_t addr, padding, n_chunks, color = 0; + size_t page_size; + int errsv; + void * aligned_memory; + Uint8 *mem; + unsigned int i; + + page_size = allocator_aligned_page_size (allocator, SLAB_BPAGE_SIZE (allocator, chunk_size)); + /* allocate 1 page for the chunks and the slab */ + aligned_memory = allocator_memalign (page_size, page_size - NATIVE_MALLOC_PADDING); + errsv = errno; + mem = aligned_memory; + + if (!mem) + { + const char *syserr = strerror (errsv); + mem_error ("failed to allocate %u bytes (alignment: %u): %s\n", + (unsigned int) (page_size - NATIVE_MALLOC_PADDING), (unsigned int) page_size, syserr); + } + /* mask page address */ + addr = ((size_t) mem / page_size) * page_size; + /* assert alignment */ + mem_assert (aligned_memory == (void *) addr); + /* basic slab info setup */ + sinfo = (SlabInfo*) (mem + page_size - SLAB_INFO_SIZE); + sinfo->n_allocated = 0; + sinfo->chunks = NULL; + /* figure cache colorization */ + n_chunks = ((Uint8*) sinfo - mem) / chunk_size; + padding = ((Uint8*) sinfo - mem) - n_chunks * chunk_size; + if (padding) + { + color = (allocator->color_accu * P2ALIGNMENT) % padding; + allocator->color_accu += allocator->config.color_increment; + } + /* add chunks to free list */ + chunk = (ChunkLink*) (mem + color); + sinfo->chunks = chunk; + for (i = 0; i < n_chunks - 1; i++) + { + chunk->next = (ChunkLink*) ((Uint8*) chunk + chunk_size); + chunk = chunk->next; + } + chunk->next = NULL; /* last chunk */ + /* add slab to slab ring */ + allocator_slab_stack_push (allocator, ix, sinfo); +} + +static void *slab_allocator_alloc_chunk(size_t chunk_size) +{ + ChunkLink *chunk; + unsigned int ix = SLAB_INDEX (allocator, chunk_size); + /* ensure non-empty slab */ + if (!allocator->slab_stack[ix] || !allocator->slab_stack[ix]->chunks) + allocator_add_slab (allocator, ix, chunk_size); + /* allocate chunk */ + chunk = allocator->slab_stack[ix]->chunks; + allocator->slab_stack[ix]->chunks = chunk->next; + allocator->slab_stack[ix]->n_allocated++; + /* rotate empty slabs */ + if (!allocator->slab_stack[ix]->chunks) + allocator->slab_stack[ix] = allocator->slab_stack[ix]->next; + return chunk; +} + +static void slab_allocator_free_chunk(size_t chunk_size, void *mem) +{ + ChunkLink *chunk; + BOOL was_empty; + unsigned int ix = SLAB_INDEX (allocator, chunk_size); + size_t page_size = allocator_aligned_page_size (allocator, SLAB_BPAGE_SIZE (allocator, chunk_size)); + size_t addr = ((size_t) mem / page_size) * page_size; + /* mask page address */ + Uint8 *page = (Uint8*) addr; + SlabInfo *sinfo = (SlabInfo*) (page + page_size - SLAB_INFO_SIZE); + /* assert valid chunk count */ + mem_assert (sinfo->n_allocated > 0); + /* add chunk to free list */ + was_empty = sinfo->chunks == NULL; + chunk = (ChunkLink*) mem; + chunk->next = sinfo->chunks; + sinfo->chunks = chunk; + sinfo->n_allocated--; + /* keep slab ring partially sorted, empty slabs at end */ + if (was_empty) + { + /* unlink slab */ + SlabInfo *next = sinfo->next, *prev = sinfo->prev; + next->prev = prev; + prev->next = next; + if (allocator->slab_stack[ix] == sinfo) + allocator->slab_stack[ix] = next == sinfo ? NULL : next; + /* insert slab at head */ + allocator_slab_stack_push (allocator, ix, sinfo); + } + /* eagerly free complete unused slabs */ + if (!sinfo->n_allocated) + { + /* unlink slab */ + SlabInfo *next = sinfo->next, *prev = sinfo->prev; + next->prev = prev; + prev->next = next; + if (allocator->slab_stack[ix] == sinfo) + allocator->slab_stack[ix] = next == sinfo ? NULL : next; + /* free slab */ + allocator_memfree (page_size, page); + } +} + +/* --- memalign implementation --- */ +#ifdef HAVE_MALLOC_H +#include /* memalign() */ +#endif + +/* from config.h: + * define HAVE_POSIX_MEMALIGN 1 // if free(posix_memalign(3)) works, + * define HAVE_MEMALIGN 1 // if free(memalign(3)) works, + * define HAVE_VALLOC 1 // if free(valloc(3)) works, or + * if none is provided, we implement malloc(3)-based alloc-only page alignment + */ + +#if !(HAVE_POSIX_MEMALIGN || HAVE_MEMALIGN || HAVE_VALLOC) + +typedef struct _GTrashStack GTrashStack; +struct _GTrashStack +{ + GTrashStack *next; +}; + +static GTrashStack *compat_valloc_trash = NULL; + +static void g_trash_stack_push (GTrashStack **stack_p, + void *data_p) +{ + GTrashStack *data = (GTrashStack *) data_p; + + data->next = *stack_p; + *stack_p = data; +} + +void *g_trash_stack_pop (GTrashStack **stack_p) +{ + GTrashStack *data; + + data = *stack_p; + if (data) { + *stack_p = data->next; + /* NULLify private pointer here, most platforms store NULL as + * subsequent 0 bytes + */ + data->next = NULL; + } + + return data; +} +#endif + +static void *allocator_memalign (size_t alignment, size_t memsize) +{ + void * aligned_memory = NULL; + int err = ENOMEM; +#if HAVE_POSIX_MEMALIGN + err = posix_memalign (&aligned_memory, alignment, memsize); +#elif HAVE_MEMALIGN + errno = 0; + aligned_memory = memalign (alignment, memsize); + err = errno; +#elif HAVE_VALLOC + errno = 0; + aligned_memory = valloc (memsize); + err = errno; +#else + /* simplistic non-freeing page allocator */ + mem_assert (alignment == sys_page_size); + mem_assert (memsize <= sys_page_size); + if (!compat_valloc_trash) + { + const unsigned int n_pages = 16; + Uint8 *mem = malloc (n_pages * sys_page_size); + err = errno; + if (mem) { + int i = n_pages; + Uint8 *amem = (Uint8*) ALIGN ((size_t) mem, sys_page_size); + if (amem != mem) + i--; /* mem wasn't page aligned */ + while (--i >= 0) + g_trash_stack_push (&compat_valloc_trash, amem + i * sys_page_size); + } + } + + aligned_memory = g_trash_stack_pop (&compat_valloc_trash); +#endif + if (!aligned_memory) + errno = err; + return aligned_memory; +} + +static void allocator_memfree (size_t memsize, void * mem) +{ +#if HAVE_POSIX_MEMALIGN || HAVE_MEMALIGN || HAVE_VALLOC + free (mem); +#else + mem_assert (memsize <= sys_page_size); + g_trash_stack_push (&compat_valloc_trash, mem); +#endif +} + +static void mem_error (const char *format, ...) +{ + va_list args; + /* at least, put out "MEMORY-ERROR", in case we segfault during the rest of the function */ + _ERR_PRINTF("\n***SLICED-MEMORY-ERROR***: "); + va_start (args, format); + vfprintf (stderr, format, args); + va_end (args); + _ERR_PRINTF("\n"); + abort(); + _exit (1); +} + +#ifdef _MGDEVEL_MODE +/* --- g-slice memory checker tree --- */ +typedef size_t SmcKType; /* key type */ +typedef size_t SmcVType; /* value type */ +typedef struct { + SmcKType key; + SmcVType value; +} SmcEntry; + +static void smc_tree_insert (SmcKType key, SmcVType value); +static BOOL smc_tree_lookup (SmcKType key, SmcVType *value_p); +static BOOL smc_tree_remove (SmcKType key); + +/* --- g-slice memory checker implementation --- */ +static void smc_notify_alloc (void *pointer, size_t size) +{ + size_t address = (size_t) pointer; + if (pointer) + smc_tree_insert (address, size); +} + +static int smc_notify_free (void *pointer, size_t size) +{ + size_t address = (size_t) pointer; + SmcVType real_size; + BOOL found_one; + + if (!pointer) + return 1; /* ignore */ + + found_one = smc_tree_lookup (address, &real_size); + + if (!found_one) { + _ERR_PRINTF("GSlice: MemChecker: attempt to release non-allocated block: %p size=%lu\n", + pointer, size); + return 0; + } + + if (real_size != size && (real_size || size)) { + _ERR_PRINTF("GSlice: MemChecker: attempt to release block with invalid size: %p size=%lu invalid-size=%lu\n", + pointer, real_size, size); + return 0; + } + + if (!smc_tree_remove (address)) { + _ERR_PRINTF("GSlice: MemChecker: attempt to release non-allocated block: %p size=%lu\n", + pointer, size); + return 0; + } + + return 1; /* all fine */ +} + +/* --- g-slice memory checker tree implementation --- */ +#define SMC_TRUNK_COUNT (4093 /* 16381 */) /* prime, to distribute trunk collisions (big, allocated just once) */ +#define SMC_BRANCH_COUNT (511) /* prime, to distribute branch collisions */ +#define SMC_TRUNK_EXTENT (SMC_BRANCH_COUNT * 2039) /* key address space per trunk, should distribute uniformly across BRANCH_COUNT */ +#define SMC_TRUNK_HASH(k) ((k / SMC_TRUNK_EXTENT) % SMC_TRUNK_COUNT) /* generate new trunk hash per megabyte (roughly) */ +#define SMC_BRANCH_HASH(k) (k % SMC_BRANCH_COUNT) + +static pthread_mutex_t smc_tree_mutex; /* mutex for MG_SLICE=debug-blocks */ + +typedef struct { + SmcEntry *entries; + unsigned int n_entries; +} SmcBranch; + +static SmcBranch **smc_tree_root = NULL; + +static void smc_tree_abort (int errval) +{ + const char *syserr = strerror (errval); + mem_error ("MemChecker: failure in debugging tree: %s", syserr); +} + +static inline SmcEntry* smc_tree_branch_grow_L(SmcBranch *branch, unsigned int index) +{ + unsigned int old_size = branch->n_entries * sizeof (branch->entries[0]); + unsigned int new_size = old_size + sizeof (branch->entries[0]); + SmcEntry *entry; + mem_assert (index <= branch->n_entries); + branch->entries = (SmcEntry*) realloc (branch->entries, new_size); + if (!branch->entries) + smc_tree_abort (errno); + entry = branch->entries + index; + memmove (entry + 1, entry, (branch->n_entries - index) * sizeof (entry[0])); + branch->n_entries += 1; + return entry; +} + +static inline SmcEntry* smc_tree_branch_lookup_nearest_L (SmcBranch *branch, SmcKType key) +{ + unsigned int n_nodes = branch->n_entries, offs = 0; + SmcEntry *check = branch->entries; + int cmp = 0; + + while (offs < n_nodes) { + unsigned int i = (offs + n_nodes) >> 1; + check = branch->entries + i; + cmp = key < check->key ? -1 : key != check->key; + if (cmp == 0) + return check; /* return exact match */ + else if (cmp < 0) + n_nodes = i; + else /* (cmp > 0) */ + offs = i + 1; + } + + /* check points at last mismatch, cmp > 0 indicates greater key */ + return cmp > 0 ? check + 1 : check; /* return insertion position for inexact match */ +} + +static void smc_tree_insert (SmcKType key, SmcVType value) +{ + unsigned int ix0, ix1; + SmcEntry *entry; + + pthread_mutex_lock (&smc_tree_mutex); + ix0 = SMC_TRUNK_HASH (key); + ix1 = SMC_BRANCH_HASH (key); + if (!smc_tree_root) { + smc_tree_root = calloc (SMC_TRUNK_COUNT, sizeof (smc_tree_root[0])); + if (!smc_tree_root) + smc_tree_abort (errno); + } + + if (!smc_tree_root[ix0]) { + smc_tree_root[ix0] = calloc (SMC_BRANCH_COUNT, sizeof (smc_tree_root[0][0])); + if (!smc_tree_root[ix0]) + smc_tree_abort (errno); + } + + entry = smc_tree_branch_lookup_nearest_L (&smc_tree_root[ix0][ix1], key); + if (!entry || /* need create */ + entry >= smc_tree_root[ix0][ix1].entries + smc_tree_root[ix0][ix1].n_entries || /* need append */ + entry->key != key) /* need insert */ + entry = smc_tree_branch_grow_L (&smc_tree_root[ix0][ix1], entry - smc_tree_root[ix0][ix1].entries); + entry->key = key; + entry->value = value; + pthread_mutex_unlock (&smc_tree_mutex); +} + +static BOOL smc_tree_lookup (SmcKType key, SmcVType *value_p) +{ + SmcEntry *entry = NULL; + unsigned int ix0 = SMC_TRUNK_HASH (key), ix1 = SMC_BRANCH_HASH (key); + BOOL found_one = FALSE; + *value_p = 0; + pthread_mutex_lock (&smc_tree_mutex); + + if (smc_tree_root && smc_tree_root[ix0]) { + entry = smc_tree_branch_lookup_nearest_L (&smc_tree_root[ix0][ix1], key); + if (entry && + entry < smc_tree_root[ix0][ix1].entries + smc_tree_root[ix0][ix1].n_entries && + entry->key == key) { + found_one = TRUE; + *value_p = entry->value; + } + } + pthread_mutex_unlock (&smc_tree_mutex); + return found_one; +} + +static BOOL smc_tree_remove (SmcKType key) +{ + unsigned int ix0 = SMC_TRUNK_HASH (key), ix1 = SMC_BRANCH_HASH (key); + BOOL found_one = FALSE; + + pthread_mutex_lock (&smc_tree_mutex); + if (smc_tree_root && smc_tree_root[ix0]) { + SmcEntry *entry = smc_tree_branch_lookup_nearest_L (&smc_tree_root[ix0][ix1], key); + if (entry && + entry < smc_tree_root[ix0][ix1].entries + smc_tree_root[ix0][ix1].n_entries && + entry->key == key) { + unsigned int i = entry - smc_tree_root[ix0][ix1].entries; + smc_tree_root[ix0][ix1].n_entries -= 1; + memmove (entry, entry + 1, (smc_tree_root[ix0][ix1].n_entries - i) * sizeof (entry[0])); + if (!smc_tree_root[ix0][ix1].n_entries) { + /* avoid useless pressure on the memory system */ + free (smc_tree_root[ix0][ix1].entries); + smc_tree_root[ix0][ix1].entries = NULL; + } + found_one = TRUE; + } + } + + pthread_mutex_unlock (&smc_tree_mutex); + return found_one; +} + +void mmg_slice_debug_tree_statistics (void) +{ + pthread_mutex_lock (&smc_tree_mutex); + + if (smc_tree_root) { + unsigned int i, j, t = 0, o = 0, b = 0, su = 0, ex = 0, en = 4294967295u; + double tf, bf; + for (i = 0; i < SMC_TRUNK_COUNT; i++) + if (smc_tree_root[i]) + { + t++; + for (j = 0; j < SMC_BRANCH_COUNT; j++) + if (smc_tree_root[i][j].n_entries) + { + b++; + su += smc_tree_root[i][j].n_entries; + en = MIN (en, smc_tree_root[i][j].n_entries); + ex = MAX (ex, smc_tree_root[i][j].n_entries); + } + else if (smc_tree_root[i][j].entries) + o++; /* formerly used, now empty */ + } + en = b ? en : 0; + tf = MAX (t, 1.0); /* max(1) to be a valid divisor */ + bf = MAX (b, 1.0); /* max(1) to be a valid divisor */ + _MG_PRINTF("GSlice: MemChecker: %u trunks, %u branches, %u old branches\n", t, b, o); + _MG_PRINTF("GSlice: MemChecker: %f branches per trunk, %.2f%% utilization\n", + b / tf, + 100.0 - (SMC_BRANCH_COUNT - b / tf) / (0.01 * SMC_BRANCH_COUNT)); + _MG_PRINTF("GSlice: MemChecker: %f entries per branch, %u minimum, %u maximum\n", + su / bf, en, ex); + } + else + _MG_PRINTF("GSlice: MemChecker: root=NULL\n"); + + pthread_mutex_unlock (&smc_tree_mutex); + + /* sample statistics (beast + MGSLice + 24h scripted core & GUI activity): + * PID %CPU %MEM VSZ RSS COMMAND + * 8887 30.3 45.8 456068 414856 beast-0.7.1 empty.bse + * $ cat /proc/8887/statm # total-program-size resident-set-size shared-pages text/code data/stack library dirty-pages + * 114017 103714 2354 344 0 108676 0 + * $ cat /proc/8887/status + * Name: beast-0.7.1 + * VmSize: 456068 kB + * VmLck: 0 kB + * VmRSS: 414856 kB + * VmData: 434620 kB + * VmStk: 84 kB + * VmExe: 1376 kB + * VmLib: 13036 kB + * VmPTE: 456 kB + * Threads: 3 + * (gdb) print mg_slice_debug_tree_statistics () + * GSlice: MemChecker: 422 trunks, 213068 branches, 0 old branches + * GSlice: MemChecker: 504.900474 branches per trunk, 98.81% utilization + * GSlice: MemChecker: 4.965039 entries per branch, 1 minimum, 37 maximum + */ +} + +#endif /* _MGDEVEL_MODE */ diff --git a/src/misc/misc.c b/src/misc/misc.c index b107ef3d..3538047c 100644 --- a/src/misc/misc.c +++ b/src/misc/misc.c @@ -235,7 +235,7 @@ BOOL mg_InitMgEtc (void) "or bad files!\n"); return FALSE; } - + if (hMgEtc) return TRUE;