diff options
author | Tim Janik <timj@imendio.com> | 2005-12-01 16:34:33 +0000 |
---|---|---|
committer | Tim Janik <timj@src.gnome.org> | 2005-12-01 16:34:33 +0000 |
commit | 733b1789c17ab4c6378ce0e9594f62dda1473ebb (patch) | |
tree | 3e8d953d07b8a5e06aaa1d06c3889ccd832a3329 | |
parent | d871f19c3074f37df65b6d304f4eacbe19c4b46c (diff) |
new slice allocator implementation.
Thu Dec 1 17:32:46 2005 Tim Janik <timj@imendio.com>
* glib/gslice.[hc]: new slice allocator implementation.
* tests/slice-test.c: added random slice allocation test.
* glib/gthread.[hc]: removed newly added private thread mem API.
* glib/gthreadinit.h:
* glib/gmessages.c:
* glib/gthread.c:
* glib/gmem.c: divided glib threading initialisation into three phases,
initialisation where private keys and messaging are not available (only
needed by gmem.c), initialisation without messaging but private keys
available (gslice.c, gmessage.c), and full fledged initialisers that
server the rest of glib. initialisation functions got renamed to reflect
the limitations of their corresponding phases.
* glib/gmem.c: removed memchunk code, defer allocations to
g_slice_* instead.
* glib/gmem.[hc]: removed g_slice_* skeletons.
* glib/glib.symbols: added g_slice_* symbols.
* configure.in: check for availability of posix_memalign(3), memalign(3)
and valloc(3).
* glib/Makefile.am: added gslice.[hc].
-rw-r--r-- | ChangeLog | 30 | ||||
-rw-r--r-- | ChangeLog.pre-2-10 | 30 | ||||
-rw-r--r-- | ChangeLog.pre-2-12 | 30 | ||||
-rw-r--r-- | configure.in | 3 | ||||
-rw-r--r-- | glib/Makefile.am | 2 | ||||
-rw-r--r-- | glib/glib.symbols | 16 | ||||
-rw-r--r-- | glib/gmem.c | 736 | ||||
-rw-r--r-- | glib/gmem.h | 14 | ||||
-rw-r--r-- | glib/gmessages.c | 10 | ||||
-rw-r--r-- | glib/gslice.c | 983 | ||||
-rw-r--r-- | glib/gslice.h | 70 | ||||
-rw-r--r-- | glib/gthread.c | 52 | ||||
-rw-r--r-- | glib/gthread.h | 4 | ||||
-rw-r--r-- | glib/gthreadinit.h | 16 | ||||
-rw-r--r-- | tests/Makefile.am | 2 | ||||
-rw-r--r-- | tests/slice-test.c | 168 |
16 files changed, 1406 insertions, 760 deletions
@@ -1,3 +1,33 @@ +Thu Dec 1 17:32:46 2005 Tim Janik <timj@imendio.com> + + * glib/gslice.[hc]: new slice allocator implementation. + + * tests/slice-test.c: added random slice allocation test. + + * glib/gthread.[hc]: removed newly added private thread mem API. + + * glib/gthreadinit.h: + * glib/gmessages.c: + * glib/gthread.c: + * glib/gmem.c: divided glib threading initialisation into three phases, + initialisation where private keys and messaging are not available (only + needed by gmem.c), initialisation without messaging but private keys + available (gslice.c, gmessage.c), and full fledged initialisers that + server the rest of glib. initialisation functions got renamed to reflect + the limitations of their corresponding phases. + + * glib/gmem.c: removed memchunk code, defer allocations to + g_slice_* instead. + + * glib/gmem.[hc]: removed g_slice_* skeletons. + + * glib/glib.symbols: added g_slice_* symbols. + + * configure.in: check for availability of posix_memalign(3), memalign(3) + and valloc(3). + + * glib/Makefile.am: added gslice.[hc]. + 2005-12-01 Tor Lillqvist <tml@novell.com> * glib/gstdio.c (g_stat): In the Win32 implementation, strip diff --git a/ChangeLog.pre-2-10 b/ChangeLog.pre-2-10 index fcbeee22a..d1cfe5c94 100644 --- a/ChangeLog.pre-2-10 +++ b/ChangeLog.pre-2-10 @@ -1,3 +1,33 @@ +Thu Dec 1 17:32:46 2005 Tim Janik <timj@imendio.com> + + * glib/gslice.[hc]: new slice allocator implementation. + + * tests/slice-test.c: added random slice allocation test. + + * glib/gthread.[hc]: removed newly added private thread mem API. + + * glib/gthreadinit.h: + * glib/gmessages.c: + * glib/gthread.c: + * glib/gmem.c: divided glib threading initialisation into three phases, + initialisation where private keys and messaging are not available (only + needed by gmem.c), initialisation without messaging but private keys + available (gslice.c, gmessage.c), and full fledged initialisers that + server the rest of glib. initialisation functions got renamed to reflect + the limitations of their corresponding phases. + + * glib/gmem.c: removed memchunk code, defer allocations to + g_slice_* instead. + + * glib/gmem.[hc]: removed g_slice_* skeletons. + + * glib/glib.symbols: added g_slice_* symbols. + + * configure.in: check for availability of posix_memalign(3), memalign(3) + and valloc(3). + + * glib/Makefile.am: added gslice.[hc]. + 2005-12-01 Tor Lillqvist <tml@novell.com> * glib/gstdio.c (g_stat): In the Win32 implementation, strip diff --git a/ChangeLog.pre-2-12 b/ChangeLog.pre-2-12 index fcbeee22a..d1cfe5c94 100644 --- a/ChangeLog.pre-2-12 +++ b/ChangeLog.pre-2-12 @@ -1,3 +1,33 @@ +Thu Dec 1 17:32:46 2005 Tim Janik <timj@imendio.com> + + * glib/gslice.[hc]: new slice allocator implementation. + + * tests/slice-test.c: added random slice allocation test. + + * glib/gthread.[hc]: removed newly added private thread mem API. + + * glib/gthreadinit.h: + * glib/gmessages.c: + * glib/gthread.c: + * glib/gmem.c: divided glib threading initialisation into three phases, + initialisation where private keys and messaging are not available (only + needed by gmem.c), initialisation without messaging but private keys + available (gslice.c, gmessage.c), and full fledged initialisers that + server the rest of glib. initialisation functions got renamed to reflect + the limitations of their corresponding phases. + + * glib/gmem.c: removed memchunk code, defer allocations to + g_slice_* instead. + + * glib/gmem.[hc]: removed g_slice_* skeletons. + + * glib/glib.symbols: added g_slice_* symbols. + + * configure.in: check for availability of posix_memalign(3), memalign(3) + and valloc(3). + + * glib/Makefile.am: added gslice.[hc]. + 2005-12-01 Tor Lillqvist <tml@novell.com> * glib/gstdio.c (g_stat): In the Win32 implementation, strip diff --git a/configure.in b/configure.in index eb4497adc..fa2f964cb 100644 --- a/configure.in +++ b/configure.in @@ -523,6 +523,9 @@ AC_HEADER_STDC AC_FUNC_VPRINTF AC_FUNC_MMAP AC_FUNC_ALLOCA +AC_CHECK_FUNCS(posix_memalign) +AC_CHECK_FUNCS(memalign) +AC_CHECK_FUNCS(valloc) AC_CHECK_FUNCS(atexit on_exit) diff --git a/glib/Makefile.am b/glib/Makefile.am index 1df900359..711bf0511 100644 --- a/glib/Makefile.am +++ b/glib/Makefile.am @@ -104,6 +104,7 @@ libglib_2_0_la_SOURCES = \ grand.c \ gscanner.c \ gshell.c \ + gslice.c \ gslist.c \ gstdio.c \ gstrfuncs.c \ @@ -179,6 +180,7 @@ glibsubinclude_HEADERS = \ grel.h \ gscanner.h \ gshell.h \ + gslice.h \ gslist.h \ gspawn.h \ gstdio.h \ diff --git a/glib/glib.symbols b/glib/glib.symbols index 1cab86f63..cde48830b 100644 --- a/glib/glib.symbols +++ b/glib/glib.symbols @@ -584,10 +584,6 @@ g_realloc g_try_malloc G_GNUC_MALLOC g_try_malloc0 G_GNUC_MALLOC g_try_realloc -g_slice_alloc -g_slice_alloc0 -g_slice_free1 -g_slice_free_chain #ifndef G_DISABLE_DEPRECATED g_allocator_free g_allocator_new @@ -605,6 +601,18 @@ g_blow_chunks #endif #endif +#if IN_HEADER(__G_SLICE_H__) +#if IN_FILE(__G_SLICE_C__) +g_slice_alloc G_GNUC_MALLOC +g_slice_alloc0 G_GNUC_MALLOC +g_slice_free1 +g_slice_free_chain +g_slice_set_config +g_slice_get_config +g_slice_get_config_state +#endif +#endif + #if IN_HEADER(__G_MESSAGES_H__) #if IN_FILE(__G_MESSAGES_C__) g_printf_string_upper_bound diff --git a/glib/gmem.c b/glib/gmem.c index 68de9057e..849ade515 100644 --- a/glib/gmem.c +++ b/glib/gmem.c @@ -39,8 +39,6 @@ #include "galias.h" /* notes on macros: - * having DISABLE_MEM_POOLS defined, disables mem_chunks alltogether, their - * allocations are performed through ordinary g_malloc/g_free. * having G_DISABLE_CHECKS defined disables use of glib_mem_profiler_table and * g_mem_profile(). * REALLOC_0_WORKS is defined if g_realloc (NULL, x) works. @@ -51,19 +49,6 @@ #define MEM_PROFILE_TABLE_SIZE 4096 -#define MEM_AREA_SIZE 4L - -#ifdef G_DISABLE_CHECKS -# define ENTER_MEM_CHUNK_ROUTINE() -# define LEAVE_MEM_CHUNK_ROUTINE() -# define IN_MEM_CHUNK_ROUTINE() FALSE -#else /* !G_DISABLE_CHECKS */ -static GPrivate* mem_chunk_recursion = NULL; -# define MEM_CHUNK_ROUTINE_COUNT() GPOINTER_TO_UINT (g_private_get (mem_chunk_recursion)) -# define ENTER_MEM_CHUNK_ROUTINE() g_private_set (mem_chunk_recursion, GUINT_TO_POINTER (MEM_CHUNK_ROUTINE_COUNT () + 1)) -# define LEAVE_MEM_CHUNK_ROUTINE() g_private_set (mem_chunk_recursion, GUINT_TO_POINTER (MEM_CHUNK_ROUTINE_COUNT () - 1)) -#endif /* !G_DISABLE_CHECKS */ - #ifndef REALLOC_0_WORKS static gpointer standard_realloc (gpointer mem, @@ -295,11 +280,9 @@ typedef enum { } ProfilerJob; static guint *profile_data = NULL; static gulong profile_allocs = 0; -static gulong profile_mc_allocs = 0; static gulong profile_zinit = 0; static gulong profile_frees = 0; -static gulong profile_mc_frees = 0; -static GMutex *g_profile_mutex = NULL; +static GMutex *gmem_profile_mutex = NULL; #ifdef G_ENABLE_DEBUG static volatile gulong g_trap_free_size = 0; static volatile gulong g_trap_realloc_size = 0; @@ -313,47 +296,37 @@ profiler_log (ProfilerJob job, gulong n_bytes, gboolean success) { - g_mutex_lock (g_profile_mutex); + g_mutex_lock (gmem_profile_mutex); if (!profile_data) { profile_data = standard_malloc ((MEM_PROFILE_TABLE_SIZE + 1) * 8 * sizeof (profile_data[0])); if (!profile_data) /* memory system kiddin' me, eh? */ { - g_mutex_unlock (g_profile_mutex); + g_mutex_unlock (gmem_profile_mutex); return; } } - if (MEM_CHUNK_ROUTINE_COUNT () == 0) - { - if (n_bytes < MEM_PROFILE_TABLE_SIZE) - profile_data[n_bytes + PROFILE_TABLE ((job & PROFILER_ALLOC) != 0, - (job & PROFILER_RELOC) != 0, - success != 0)] += 1; - else - profile_data[MEM_PROFILE_TABLE_SIZE + PROFILE_TABLE ((job & PROFILER_ALLOC) != 0, - (job & PROFILER_RELOC) != 0, - success != 0)] += 1; - if (success) - { - if (job & PROFILER_ALLOC) - { - profile_allocs += n_bytes; - if (job & PROFILER_ZINIT) - profile_zinit += n_bytes; - } - else - profile_frees += n_bytes; - } - } - else if (success) + if (n_bytes < MEM_PROFILE_TABLE_SIZE) + profile_data[n_bytes + PROFILE_TABLE ((job & PROFILER_ALLOC) != 0, + (job & PROFILER_RELOC) != 0, + success != 0)] += 1; + else + profile_data[MEM_PROFILE_TABLE_SIZE + PROFILE_TABLE ((job & PROFILER_ALLOC) != 0, + (job & PROFILER_RELOC) != 0, + success != 0)] += 1; + if (success) { if (job & PROFILER_ALLOC) - profile_mc_allocs += n_bytes; + { + profile_allocs += n_bytes; + if (job & PROFILER_ZINIT) + profile_zinit += n_bytes; + } else - profile_mc_frees += n_bytes; + profile_frees += n_bytes; } - g_mutex_unlock (g_profile_mutex); + g_mutex_unlock (gmem_profile_mutex); } static void @@ -399,27 +372,23 @@ g_mem_profile (void) gulong local_allocs; gulong local_zinit; gulong local_frees; - gulong local_mc_allocs; - gulong local_mc_frees; - g_mutex_lock (g_profile_mutex); + g_mutex_lock (gmem_profile_mutex); local_allocs = profile_allocs; local_zinit = profile_zinit; local_frees = profile_frees; - local_mc_allocs = profile_mc_allocs; - local_mc_frees = profile_mc_frees; if (!profile_data) { - g_mutex_unlock (g_profile_mutex); + g_mutex_unlock (gmem_profile_mutex); return; } memcpy (local_data, profile_data, (MEM_PROFILE_TABLE_SIZE + 1) * 8 * sizeof (profile_data[0])); - g_mutex_unlock (g_profile_mutex); + g_mutex_unlock (gmem_profile_mutex); g_print ("GLib Memory statistics (successful operations):\n"); profile_print_locked (local_data, TRUE); @@ -432,11 +401,6 @@ g_mem_profile (void) local_frees, ((gdouble) local_frees) / local_allocs * 100.0, local_allocs - local_frees); - g_print ("MemChunk bytes: allocated=%lu, freed=%lu (%.2f%%), remaining=%lu\n", - local_mc_allocs, - local_mc_frees, - ((gdouble) local_mc_frees) / local_mc_allocs * 100.0, - local_mc_allocs - local_mc_frees); } static gpointer @@ -602,47 +566,6 @@ GMemVTable *glib_mem_profiler_table = &profiler_table; #endif /* !G_DISABLE_CHECKS */ -/* --- memory slices --- */ -typedef struct { - gpointer dummy, next; -} MemSlice; - -gpointer -g_slice_alloc (guint block_size) -{ - return g_malloc (block_size); -} - -gpointer -g_slice_alloc0 (guint block_size) -{ - return g_malloc0 (block_size); -} - -void -g_slice_free1 (guint block_size, - gpointer mem_block) -{ - if (mem_block) - g_free (mem_block); -} - -void -g_slice_free_chain (guint block_size, - gpointer mem_chain, - guint next_offset) -{ - MemSlice *slice = mem_chain; - g_return_if_fail (next_offset == G_STRUCT_OFFSET (MemSlice, next)); - g_return_if_fail (block_size >= sizeof (MemSlice)); - while (slice) - { - MemSlice *current = slice; - slice = slice->next; - g_slice_free1 (block_size, current); - } -} - /* --- MemChunks --- */ #ifndef G_ALLOC_AND_FREE typedef struct _GAllocator GAllocator; @@ -651,63 +574,10 @@ typedef struct _GMemChunk GMemChunk; #define G_ALLOC_AND_FREE 2 #endif -typedef struct _GFreeAtom GFreeAtom; -typedef struct _GMemArea GMemArea; - -struct _GFreeAtom -{ - GFreeAtom *next; -}; - -struct _GMemArea -{ - GMemArea *next; /* the next mem area */ - GMemArea *prev; /* the previous mem area */ - gulong index; /* the current index into the "mem" array */ - gulong free; /* the number of free bytes in this mem area */ - gulong allocated; /* the number of atoms allocated from this area */ - gulong mark; /* is this mem area marked for deletion */ - gchar mem[MEM_AREA_SIZE]; /* the mem array from which atoms get allocated - * the actual size of this array is determined by - * the mem chunk "area_size". ANSI says that it - * must be declared to be the maximum size it - * can possibly be (even though the actual size - * may be less). - */ -}; - -struct _GMemChunk -{ - const gchar *name; /* name of this MemChunk...used for debugging output */ - gint type; /* the type of MemChunk: ALLOC_ONLY or ALLOC_AND_FREE */ - gint num_mem_areas; /* the number of memory areas */ - gint num_marked_areas; /* the number of areas marked for deletion */ - guint atom_size; /* the size of an atom */ - gulong area_size; /* the size of a memory area */ - GMemArea *mem_area; /* the current memory area */ - GMemArea *mem_areas; /* a list of all the mem areas owned by this chunk */ - GMemArea *free_mem_area; /* the free area...which is about to be destroyed */ - GFreeAtom *free_atoms; /* the free atoms list */ - GTree *mem_tree; /* tree of mem areas sorted by memory address */ - GMemChunk *next; /* pointer to the next chunk */ - GMemChunk *prev; /* pointer to the previous chunk */ +struct _GMemChunk { + guint alloc_size; /* the size of an atom */ }; - -#ifndef DISABLE_MEM_POOLS -static gulong g_mem_chunk_compute_size (gulong size, - gulong min_size) G_GNUC_CONST; -static gint g_mem_chunk_area_compare (GMemArea *a, - GMemArea *b); -static gint g_mem_chunk_area_search (GMemArea *a, - gchar *addr); - -/* here we can't use StaticMutexes, as they depend upon a working - * g_malloc, the same holds true for StaticPrivate - */ -static GMutex *mem_chunks_lock = NULL; -static GMemChunk *mem_chunks = NULL; - GMemChunk* g_mem_chunk_new (const gchar *name, gint atom_size, @@ -715,509 +585,11 @@ g_mem_chunk_new (const gchar *name, gint type) { GMemChunk *mem_chunk; - gulong rarea_size; - - g_return_val_if_fail (atom_size > 0, NULL); - g_return_val_if_fail (area_size >= atom_size, NULL); - - ENTER_MEM_CHUNK_ROUTINE (); - - area_size = (area_size + atom_size - 1) / atom_size; - area_size *= atom_size; - - mem_chunk = g_new (GMemChunk, 1); - mem_chunk->name = name; - mem_chunk->type = type; - mem_chunk->num_mem_areas = 0; - mem_chunk->num_marked_areas = 0; - mem_chunk->mem_area = NULL; - mem_chunk->free_mem_area = NULL; - mem_chunk->free_atoms = NULL; - mem_chunk->mem_tree = NULL; - mem_chunk->mem_areas = NULL; - mem_chunk->atom_size = atom_size; - - if (mem_chunk->type == G_ALLOC_AND_FREE) - mem_chunk->mem_tree = g_tree_new ((GCompareFunc) g_mem_chunk_area_compare); - - if (mem_chunk->atom_size % G_MEM_ALIGN) - mem_chunk->atom_size += G_MEM_ALIGN - (mem_chunk->atom_size % G_MEM_ALIGN); - - rarea_size = area_size + sizeof (GMemArea) - MEM_AREA_SIZE; - rarea_size = g_mem_chunk_compute_size (rarea_size, atom_size + sizeof (GMemArea) - MEM_AREA_SIZE); - mem_chunk->area_size = rarea_size - (sizeof (GMemArea) - MEM_AREA_SIZE); - - g_mutex_lock (mem_chunks_lock); - mem_chunk->next = mem_chunks; - mem_chunk->prev = NULL; - if (mem_chunks) - mem_chunks->prev = mem_chunk; - mem_chunks = mem_chunk; - g_mutex_unlock (mem_chunks_lock); - - LEAVE_MEM_CHUNK_ROUTINE (); - - return mem_chunk; -} - -void -g_mem_chunk_destroy (GMemChunk *mem_chunk) -{ - GMemArea *mem_areas; - GMemArea *temp_area; - - g_return_if_fail (mem_chunk != NULL); - - ENTER_MEM_CHUNK_ROUTINE (); - - mem_areas = mem_chunk->mem_areas; - while (mem_areas) - { - temp_area = mem_areas; - mem_areas = mem_areas->next; - g_free (temp_area); - } - - g_mutex_lock (mem_chunks_lock); - if (mem_chunk->next) - mem_chunk->next->prev = mem_chunk->prev; - if (mem_chunk->prev) - mem_chunk->prev->next = mem_chunk->next; - - if (mem_chunk == mem_chunks) - mem_chunks = mem_chunks->next; - g_mutex_unlock (mem_chunks_lock); - - if (mem_chunk->type == G_ALLOC_AND_FREE) - g_tree_destroy (mem_chunk->mem_tree); - - g_free (mem_chunk); - - LEAVE_MEM_CHUNK_ROUTINE (); -} - -gpointer -g_mem_chunk_alloc (GMemChunk *mem_chunk) -{ - GMemArea *temp_area; - gpointer mem; - - ENTER_MEM_CHUNK_ROUTINE (); - - g_return_val_if_fail (mem_chunk != NULL, NULL); - - while (mem_chunk->free_atoms) - { - /* Get the first piece of memory on the "free_atoms" list. - * We can go ahead and destroy the list node we used to keep - * track of it with and to update the "free_atoms" list to - * point to its next element. - */ - mem = mem_chunk->free_atoms; - mem_chunk->free_atoms = mem_chunk->free_atoms->next; - - /* Determine which area this piece of memory is allocated from */ - temp_area = g_tree_search (mem_chunk->mem_tree, - (GCompareFunc) g_mem_chunk_area_search, - mem); - - /* If the area has been marked, then it is being destroyed. - * (ie marked to be destroyed). - * We check to see if all of the segments on the free list that - * reference this area have been removed. This occurs when - * the ammount of free memory is less than the allocatable size. - * If the chunk should be freed, then we place it in the "free_mem_area". - * This is so we make sure not to free the mem area here and then - * allocate it again a few lines down. - * If we don't allocate a chunk a few lines down then the "free_mem_area" - * will be freed. - * If there is already a "free_mem_area" then we'll just free this mem area. - */ - if (temp_area->mark) - { - /* Update the "free" memory available in that area */ - temp_area->free += mem_chunk->atom_size; - - if (temp_area->free == mem_chunk->area_size) - { - if (temp_area == mem_chunk->mem_area) - mem_chunk->mem_area = NULL; - - if (mem_chunk->free_mem_area) - { - mem_chunk->num_mem_areas -= 1; - - if (temp_area->next) - temp_area->next->prev = temp_area->prev; - if (temp_area->prev) - temp_area->prev->next = temp_area->next; - if (temp_area == mem_chunk->mem_areas) - mem_chunk->mem_areas = mem_chunk->mem_areas->next; - - if (mem_chunk->type == G_ALLOC_AND_FREE) - g_tree_remove (mem_chunk->mem_tree, temp_area); - g_free (temp_area); - } - else - mem_chunk->free_mem_area = temp_area; - - mem_chunk->num_marked_areas -= 1; - } - } - else - { - /* Update the number of allocated atoms count. - */ - temp_area->allocated += 1; - - /* The area wasn't marked...return the memory - */ - goto outa_here; - } - } - - /* If there isn't a current mem area or the current mem area is out of space - * then allocate a new mem area. We'll first check and see if we can use - * the "free_mem_area". Otherwise we'll just malloc the mem area. - */ - if ((!mem_chunk->mem_area) || - ((mem_chunk->mem_area->index + mem_chunk->atom_size) > mem_chunk->area_size)) - { - if (mem_chunk->free_mem_area) - { - mem_chunk->mem_area = mem_chunk->free_mem_area; - mem_chunk->free_mem_area = NULL; - } - else - { -#ifdef ENABLE_GC_FRIENDLY - mem_chunk->mem_area = (GMemArea*) g_malloc0 (sizeof (GMemArea) - - MEM_AREA_SIZE + - mem_chunk->area_size); -#else /* !ENABLE_GC_FRIENDLY */ - mem_chunk->mem_area = (GMemArea*) g_malloc (sizeof (GMemArea) - - MEM_AREA_SIZE + - mem_chunk->area_size); -#endif /* ENABLE_GC_FRIENDLY */ - - mem_chunk->num_mem_areas += 1; - mem_chunk->mem_area->next = mem_chunk->mem_areas; - mem_chunk->mem_area->prev = NULL; - - if (mem_chunk->mem_areas) - mem_chunk->mem_areas->prev = mem_chunk->mem_area; - mem_chunk->mem_areas = mem_chunk->mem_area; - - if (mem_chunk->type == G_ALLOC_AND_FREE) - g_tree_insert (mem_chunk->mem_tree, mem_chunk->mem_area, mem_chunk->mem_area); - } - - mem_chunk->mem_area->index = 0; - mem_chunk->mem_area->free = mem_chunk->area_size; - mem_chunk->mem_area->allocated = 0; - mem_chunk->mem_area->mark = 0; - } - - /* Get the memory and modify the state variables appropriately. - */ - mem = (gpointer) &mem_chunk->mem_area->mem[mem_chunk->mem_area->index]; - mem_chunk->mem_area->index += mem_chunk->atom_size; - mem_chunk->mem_area->free -= mem_chunk->atom_size; - mem_chunk->mem_area->allocated += 1; - -outa_here: - - LEAVE_MEM_CHUNK_ROUTINE (); - - return mem; -} - -gpointer -g_mem_chunk_alloc0 (GMemChunk *mem_chunk) -{ - gpointer mem; - - mem = g_mem_chunk_alloc (mem_chunk); - if (mem) - { - memset (mem, 0, mem_chunk->atom_size); - } - - return mem; -} - -void -g_mem_chunk_free (GMemChunk *mem_chunk, - gpointer mem) -{ - GMemArea *temp_area; - GFreeAtom *free_atom; - - g_return_if_fail (mem_chunk != NULL); - g_return_if_fail (mem != NULL); - - ENTER_MEM_CHUNK_ROUTINE (); - -#ifdef ENABLE_GC_FRIENDLY - memset (mem, 0, mem_chunk->atom_size); -#endif /* ENABLE_GC_FRIENDLY */ - - /* Don't do anything if this is an ALLOC_ONLY chunk - */ - if (mem_chunk->type == G_ALLOC_AND_FREE) - { - /* Place the memory on the "free_atoms" list - */ - free_atom = (GFreeAtom*) mem; - free_atom->next = mem_chunk->free_atoms; - mem_chunk->free_atoms = free_atom; - - temp_area = g_tree_search (mem_chunk->mem_tree, - (GCompareFunc) g_mem_chunk_area_search, - mem); - - temp_area->allocated -= 1; - - if (temp_area->allocated == 0) - { - temp_area->mark = 1; - mem_chunk->num_marked_areas += 1; - } - } - - LEAVE_MEM_CHUNK_ROUTINE (); -} - -/* This doesn't free the free_area if there is one */ -void -g_mem_chunk_clean (GMemChunk *mem_chunk) -{ - GMemArea *mem_area; - GFreeAtom *prev_free_atom; - GFreeAtom *temp_free_atom; - gpointer mem; - - g_return_if_fail (mem_chunk != NULL); - - ENTER_MEM_CHUNK_ROUTINE (); - - if (mem_chunk->type == G_ALLOC_AND_FREE) - { - prev_free_atom = NULL; - temp_free_atom = mem_chunk->free_atoms; - - while (temp_free_atom) - { - mem = (gpointer) temp_free_atom; - - mem_area = g_tree_search (mem_chunk->mem_tree, - (GCompareFunc) g_mem_chunk_area_search, - mem); - - /* If this mem area is marked for destruction then delete the - * area and list node and decrement the free mem. - */ - if (mem_area->mark) - { - if (prev_free_atom) - prev_free_atom->next = temp_free_atom->next; - else - mem_chunk->free_atoms = temp_free_atom->next; - temp_free_atom = temp_free_atom->next; - - mem_area->free += mem_chunk->atom_size; - if (mem_area->free == mem_chunk->area_size) - { - mem_chunk->num_mem_areas -= 1; - mem_chunk->num_marked_areas -= 1; - - if (mem_area->next) - mem_area->next->prev = mem_area->prev; - if (mem_area->prev) - mem_area->prev->next = mem_area->next; - if (mem_area == mem_chunk->mem_areas) - mem_chunk->mem_areas = mem_chunk->mem_areas->next; - if (mem_area == mem_chunk->mem_area) - mem_chunk->mem_area = NULL; - - if (mem_chunk->type == G_ALLOC_AND_FREE) - g_tree_remove (mem_chunk->mem_tree, mem_area); - g_free (mem_area); - } - } - else - { - prev_free_atom = temp_free_atom; - temp_free_atom = temp_free_atom->next; - } - } - } - LEAVE_MEM_CHUNK_ROUTINE (); -} - -void -g_mem_chunk_reset (GMemChunk *mem_chunk) -{ - GMemArea *mem_areas; - GMemArea *temp_area; - - g_return_if_fail (mem_chunk != NULL); - - ENTER_MEM_CHUNK_ROUTINE (); - - mem_areas = mem_chunk->mem_areas; - mem_chunk->num_mem_areas = 0; - mem_chunk->mem_areas = NULL; - mem_chunk->mem_area = NULL; - - while (mem_areas) - { - temp_area = mem_areas; - mem_areas = mem_areas->next; - g_free (temp_area); - } - - mem_chunk->free_atoms = NULL; - - if (mem_chunk->mem_tree) - { - g_tree_destroy (mem_chunk->mem_tree); - mem_chunk->mem_tree = g_tree_new ((GCompareFunc) g_mem_chunk_area_compare); - } - - LEAVE_MEM_CHUNK_ROUTINE (); -} - -void -g_mem_chunk_print (GMemChunk *mem_chunk) -{ - GMemArea *mem_areas; - gulong mem; - - g_return_if_fail (mem_chunk != NULL); - - mem_areas = mem_chunk->mem_areas; - mem = 0; - - while (mem_areas) - { - mem += mem_chunk->area_size - mem_areas->free; - mem_areas = mem_areas->next; - } - - g_log (G_LOG_DOMAIN, G_LOG_LEVEL_INFO, - "%s: %ld bytes using %d mem areas", - mem_chunk->name, mem, mem_chunk->num_mem_areas); -} - -void -g_mem_chunk_info (void) -{ - GMemChunk *mem_chunk; - gint count; - - count = 0; - g_mutex_lock (mem_chunks_lock); - mem_chunk = mem_chunks; - while (mem_chunk) - { - count += 1; - mem_chunk = mem_chunk->next; - } - g_mutex_unlock (mem_chunks_lock); - - g_log (G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "%d mem chunks", count); - - g_mutex_lock (mem_chunks_lock); - mem_chunk = mem_chunks; - g_mutex_unlock (mem_chunks_lock); - - while (mem_chunk) - { - g_mem_chunk_print ((GMemChunk*) mem_chunk); - mem_chunk = mem_chunk->next; - } -} - -void -g_blow_chunks (void) -{ - GMemChunk *mem_chunk; - - g_mutex_lock (mem_chunks_lock); - mem_chunk = mem_chunks; - g_mutex_unlock (mem_chunks_lock); - while (mem_chunk) - { - g_mem_chunk_clean ((GMemChunk*) mem_chunk); - mem_chunk = mem_chunk->next; - } -} - -static gulong -g_mem_chunk_compute_size (gulong size, - gulong min_size) -{ - gulong power_of_2; - gulong lower, upper; - - power_of_2 = 16; - while (power_of_2 < size) - power_of_2 <<= 1; - - lower = power_of_2 >> 1; - upper = power_of_2; - - if (size - lower < upper - size && lower >= min_size) - return lower; - else - return upper; -} - -static gint -g_mem_chunk_area_compare (GMemArea *a, - GMemArea *b) -{ - if (a->mem > b->mem) - return 1; - else if (a->mem < b->mem) - return -1; - return 0; -} - -static gint -g_mem_chunk_area_search (GMemArea *a, - gchar *addr) -{ - if (a->mem <= addr) - { - if (addr < &a->mem[a->index]) - return 0; - return 1; - } - return -1; -} - -#else /* DISABLE_MEM_POOLS */ - -typedef struct { - guint alloc_size; /* the size of an atom */ -} GMinimalMemChunk; - -GMemChunk* -g_mem_chunk_new (const gchar *name, - gint atom_size, - gulong area_size, - gint type) -{ - GMinimalMemChunk *mem_chunk; - g_return_val_if_fail (atom_size > 0, NULL); - mem_chunk = g_new (GMinimalMemChunk, 1); + mem_chunk = g_slice_new (GMemChunk); mem_chunk->alloc_size = atom_size; - - return ((GMemChunk*) mem_chunk); + return mem_chunk; } void @@ -1225,27 +597,23 @@ g_mem_chunk_destroy (GMemChunk *mem_chunk) { g_return_if_fail (mem_chunk != NULL); - g_free (mem_chunk); + g_slice_free (GMemChunk, mem_chunk); } gpointer g_mem_chunk_alloc (GMemChunk *mem_chunk) { - GMinimalMemChunk *minimal = (GMinimalMemChunk *)mem_chunk; - g_return_val_if_fail (mem_chunk != NULL, NULL); - return g_malloc (minimal->alloc_size); + return g_slice_alloc (mem_chunk->alloc_size); } gpointer g_mem_chunk_alloc0 (GMemChunk *mem_chunk) { - GMinimalMemChunk *minimal = (GMinimalMemChunk *)mem_chunk; - g_return_val_if_fail (mem_chunk != NULL, NULL); - return g_malloc0 (minimal->alloc_size); + return g_slice_alloc0 (mem_chunk->alloc_size); } void @@ -1254,7 +622,7 @@ g_mem_chunk_free (GMemChunk *mem_chunk, { g_return_if_fail (mem_chunk != NULL); - g_free (mem); + g_slice_free1 (mem_chunk->alloc_size, mem); } void g_mem_chunk_clean (GMemChunk *mem_chunk) {} @@ -1263,28 +631,23 @@ void g_mem_chunk_print (GMemChunk *mem_chunk) {} void g_mem_chunk_info (void) {} void g_blow_chunks (void) {} -#endif /* DISABLE_MEM_POOLS */ - -struct _GAllocator -{ - gchar *name; - guint16 n_preallocs; - guint is_unused : 1; - guint type : 4; - GAllocator *last; - GMemChunk *mem_chunk; - gpointer free_list; -}; - GAllocator* g_allocator_new (const gchar *name, guint n_preallocs) { - static const GAllocator dummy = { + static const struct _GAllocator { + gchar *name; + guint16 n_preallocs; + guint is_unused : 1; + guint type : 4; + GAllocator *last; + GMemChunk *mem_chunk; + gpointer free_list; + } dummy = { "GAllocator is deprecated", 1, TRUE, 0, NULL, NULL, NULL, }; /* some (broken) GAllocator uses depend on non-NULL allocators */ - return (GAllocator*) &dummy; + return (void*) &dummy; } void @@ -1293,22 +656,13 @@ g_allocator_free (GAllocator *allocator) } void -_g_mem_thread_init (void) -{ -#ifndef DISABLE_MEM_POOLS - mem_chunks_lock = g_mutex_new (); -#endif -#ifndef G_DISABLE_CHECKS - g_profile_mutex = g_mutex_new (); -#endif -} - -void -_g_mem_thread_private_init (void) +_g_mem_thread_init_noprivate_nomessage (void) { + /* we may only create mutexes here, locking/unlocking itself + * does not yet work. + */ #ifndef G_DISABLE_CHECKS - g_assert (mem_chunk_recursion == NULL); - mem_chunk_recursion = g_private_new (NULL); + gmem_profile_mutex = g_mutex_new (); #endif } diff --git a/glib/gmem.h b/glib/gmem.h index 6e1773ed2..ae8756ed3 100644 --- a/glib/gmem.h +++ b/glib/gmem.h @@ -27,6 +27,7 @@ #ifndef __G_MEM_H__ #define __G_MEM_H__ +#include <glib/gslice.h> #include <glib/gtypes.h> G_BEGIN_DECLS @@ -96,19 +97,6 @@ gboolean g_mem_is_system_malloc (void); GLIB_VAR GMemVTable *glib_mem_profiler_table; void g_mem_profile (void); -/* slices - fast allocation/release of small memory blocks - */ -gpointer g_slice_alloc (guint block_size); -gpointer g_slice_alloc0 (guint block_size); -void g_slice_free1 (guint block_size, - gpointer mem_block); -void g_slice_free_chain (guint block_size, - gpointer mem_chain, - guint next_offset); -#define g_slice_new(type) ((type*) g_slice_alloc (sizeof (type))) -#define g_slice_new0(type) ((type*) g_slice_alloc0 (sizeof (type))) -#define g_slice_free(type,mem) g_slice_free1 (sizeof (type), mem) - /* deprecated memchunks and allocators */ #if !defined G_DISABLE_DEPRECATED || 1 diff --git a/glib/gmessages.c b/glib/gmessages.c index 38298bfdc..726db4e92 100644 --- a/glib/gmessages.c +++ b/glib/gmessages.c @@ -1041,20 +1041,14 @@ g_printf_string_upper_bound (const gchar *format, } void -_g_messages_thread_init (void) +_g_messages_thread_init_nomessage (void) { g_messages_lock = g_mutex_new (); + g_log_depth = g_private_new (NULL); g_messages_prefixed_init (); _g_debug_init (); } -void -_g_messages_thread_private_init (void) -{ - g_assert (g_log_depth == NULL); - g_log_depth = g_private_new (NULL); -} - gboolean _g_debug_initialized = FALSE; guint _g_debug_flags = 0; diff --git a/glib/gslice.c b/glib/gslice.c new file mode 100644 index 000000000..0cc903aeb --- /dev/null +++ b/glib/gslice.c @@ -0,0 +1,983 @@ +/* GLIB sliced memory - fast concurrent memory chunk allocator + * 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 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, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +/* MT safe */ +#define _XOPEN_SOURCE 600 /* posix_memalign() */ +#include <stdlib.h> /* posix_memalign() */ +#include <unistd.h> /* sysconf() */ +#include <assert.h> /* assert() for nomessage phase */ +#include <string.h> +#include <errno.h> +#include "config.h" +#include "gmem.h" /* gslice.h */ +#include "gthreadinit.h" +#include "galias.h" +#include "glib.h" + +/* the GSlice 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 g_private_get() 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 (gsize)) /* fits 2 pointers (assumed to be 2 * GLIB_SIZEOF_SIZE_T below) */ +#define ALIGN(size, base) ((base) * (gsize) (((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 (13) /* 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_PAGE_SIZE(al,csz) (ALIGN (8 * (csz) + SLAB_INFO_SIZE, (al)->min_page_size)) + +/* optimized version of ALIGN (size, P2ALIGNMENT) */ +#if GLIB_SIZEOF_SIZE_T * 2 == 8 /* P2ALIGNMENT */ +#define P2ALIGN(size) (((size) + 0x7) & ~(gsize) 0x7) +#elif GLIB_SIZEOF_SIZE_T * 2 == 16 /* P2ALIGNMENT */ +#define P2ALIGN(size) (((size) + 0xf) & ~(gsize) 0xf) +#else +#define P2ALIGN(size) ALIGN (size, P2ALIGNMENT) +#endif + +/* --- structures --- */ +typedef struct _ChunkLink ChunkLink; +typedef struct _SlabInfo SlabInfo; +typedef struct _CachedMagazine CachedMagazine; +struct _ChunkLink { + ChunkLink *next; + ChunkLink *data; +}; +struct _SlabInfo { + ChunkLink *chunks; + guint n_allocated; + SlabInfo *next, *prev; +}; +typedef struct { + ChunkLink *chunks; + gsize 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 { + gboolean always_malloc; + gboolean bypass_magazines; + gboolean always_free; + gsize working_set_msecs; +} SliceConfig; +typedef struct { + /* const after initialization */ + gsize min_page_size, max_page_size; + SliceConfig config; + guint max_slab_chunk_size_for_magazine_cache; + /* magazine cache */ + GMutex *magazine_mutex; + ChunkLink **magazines; /* array of MAX_SLAB_INDEX (allocator) */ + guint *contention_counters; /* array of MAX_SLAB_INDEX (allocator) */ + gint mutex_counter; + guint stamp_counter; + guint last_stamp; + /* slab allocator */ + GMutex *slab_mutex; + SlabInfo **slab_stack; /* array of MAX_SLAB_INDEX (allocator) */ + guint color_accu; +} Allocator; + +/* --- prototypes --- */ +static gpointer slab_allocator_alloc_chunk (guint chunk_size); +static void slab_allocator_free_chunk (guint chunk_size, + gpointer mem); +static void private_thread_memory_cleanup (gpointer data); +static gpointer allocator_memalign (gsize alignment, + gsize memsize); +static void allocator_memfree (gsize memsize, + gpointer mem); +static inline void magazine_cache_update_stamp (void); +static inline guint allocator_get_magazine_threshold (Allocator *allocator, + guint ix); + +/* --- variables --- */ +static GPrivate *private_thread_memory = NULL; +static gsize sys_page_size = 0; +static Allocator allocator[1] = { { 0, }, }; +static SliceConfig slice_config = { + FALSE, /* always_malloc */ + FALSE, /* bypass_magazines */ + FALSE, /* always_free */ + 15 * 1000, /* working_set_msecs */ +}; + +/* --- auxillary funcitons --- */ +void +g_slice_set_config (GSliceConfig ckey, + gint64 value) +{ + g_return_if_fail (sys_page_size == 0); + switch (ckey) + { + case G_SLICE_CONFIG_ALWAYS_MALLOC: + slice_config.always_malloc = value != 0; + break; + case G_SLICE_CONFIG_BYPASS_MAGAZINES: + slice_config.bypass_magazines = value != 0; + break; + case G_SLICE_CONFIG_ALWAYS_FREE: + slice_config.always_free = value != 0; + break; + case G_SLICE_CONFIG_WORKING_SET_MSECS: + slice_config.working_set_msecs = value; + break; + default: ; + } +} + +gint64 +g_slice_get_config (GSliceConfig ckey) +{ + switch (ckey) + { + case G_SLICE_CONFIG_ALWAYS_MALLOC: + return slice_config.always_malloc; + case G_SLICE_CONFIG_BYPASS_MAGAZINES: + return slice_config.bypass_magazines; + case G_SLICE_CONFIG_ALWAYS_FREE: + return slice_config.always_free; + case G_SLICE_CONFIG_WORKING_SET_MSECS: + return slice_config.working_set_msecs; + case G_SLICE_CONFIG_CHUNK_SIZES: + return MAX_SLAB_INDEX (allocator); + default: + return 0; + } +} + +gint64* +g_slice_get_config_state (GSliceConfig ckey, + gint64 address, + guint *n_values) +{ + guint i = 0; + g_return_val_if_fail (n_values != NULL, NULL); + *n_values = 0; + switch (ckey) + { + gint64 array[64]; + case G_SLICE_CONFIG_CONTENTION_COUNTER: + array[i++] = SLAB_CHUNK_SIZE (allocator, address); + array[i++] = allocator->contention_counters[address]; + array[i++] = allocator_get_magazine_threshold (allocator, address); + *n_values = i; + return g_memdup (array, sizeof (array[0]) * *n_values); + default: + return NULL; + } +} + +static void +g_slice_init_nomessage (void) +{ + /* we may not use g_error() or friends here */ + assert (sys_page_size == 0); + + sys_page_size = sysconf (_SC_PAGESIZE); /* = sysconf (_SC_PAGE_SIZE); = getpagesize(); */ + assert (sys_page_size >= 2 * LARGEALIGNMENT); + allocator->config = slice_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. + */ + allocator->min_page_size = MAX (allocator->min_page_size, 4096); + allocator->max_page_size = MAX (allocator->min_page_size, 8192); +#else + /* we can only align to system page size */ + allocator->max_page_size = sys_page_size; +#endif + allocator->magazine_mutex = NULL; /* _g_slice_thread_init_nomessage() */ + allocator->magazines = g_new0 (ChunkLink*, MAX_SLAB_INDEX (allocator)); + allocator->contention_counters = g_new0 (guint, MAX_SLAB_INDEX (allocator)); + allocator->mutex_counter = 0; + allocator->stamp_counter = MAX_STAMP_COUNTER; /* force initial update */ + allocator->last_stamp = 0; + allocator->slab_mutex = NULL; /* _g_slice_thread_init_nomessage() */ + allocator->slab_stack = g_new0 (SlabInfo*, MAX_SLAB_INDEX (allocator)); + 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 guint +allocator_categorize (guint aligned_chunk_size) +{ + /* speed up the likely path */ + if (G_LIKELY (aligned_chunk_size && aligned_chunk_size <= allocator->max_slab_chunk_size_for_magazine_cache)) + return 1; /* use magazine cache */ + + /* the above will fail (max_slab_chunk_size_for_magazine_cache == 0) if the + * allocator is still uninitialized, or if we are not configured to use the + * magazine cache. + */ + if (!sys_page_size) + g_slice_init_nomessage (); + 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() */ +} + +void +_g_slice_thread_init_nomessage (void) +{ + /* we may not use g_error() or friends here */ + if (!sys_page_size) + g_slice_init_nomessage(); + private_thread_memory = g_private_new (private_thread_memory_cleanup); + allocator->magazine_mutex = g_mutex_new(); + allocator->slab_mutex = g_mutex_new(); +} + +static inline void +g_mutex_lock_a (GMutex *mutex, + guint *threshold) +{ + gboolean contention = FALSE; + if (!g_mutex_trylock (mutex)) + { + g_mutex_lock (mutex); + contention = TRUE; + } + if (contention) + { + allocator->mutex_counter++; + if (allocator->mutex_counter >= 1) /* quickly adapt to contention */ + { + allocator->mutex_counter = 0; + *threshold = MIN (*threshold + 1, MAX_MAGAZINE_SIZE); + } + } + else /* !contention */ + { + allocator->mutex_counter--; + if (allocator->mutex_counter < -11) /* moderately recover magazine sizes */ + { + allocator->mutex_counter = 0; + *threshold = MAX (*threshold, 1) - 1; + } + } +} + +static inline ThreadMemory* +thread_memory_from_self (void) +{ + ThreadMemory *tmem = g_private_get (private_thread_memory); + if (G_UNLIKELY (!tmem)) + { + const guint n_magazines = MAX_SLAB_INDEX (allocator); + tmem = g_malloc0 (sizeof (ThreadMemory) + sizeof (Magazine) * 2 * n_magazines); + tmem->magazine1 = (Magazine*) (tmem + 1); + tmem->magazine2 = &tmem->magazine1[n_magazines]; + g_private_set (private_thread_memory, tmem); + } + 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 (G_UNLIKELY (chunk)) + { + /* allocating from freed list */ + (*magazine_chunks)->data = chunk->next; + } + else + { + chunk = *magazine_chunks; + *magazine_chunks = chunk->next; + } + return chunk; +} + +static guint +magazine_count (ChunkLink *head) +{ + guint count = 0; + if (!head) + return 0; + while (head) + { + ChunkLink *child = head->data; + count += 1; + for (child = head->data; child; child = child->next) + count += 1; + head = head->next; + } + return count; +} + +static inline guint +allocator_get_magazine_threshold (Allocator *allocator, + guint 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 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. + */ + guint chunk_size = SLAB_CHUNK_SIZE (allocator, ix); + guint threshold = MAX (MIN_MAGAZINE_SIZE, sys_page_size / MAX (chunk_size, 64)); + guint contention_counter = allocator->contention_counters[ix]; + if (G_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) + { + GTimeVal tv; + g_get_current_time (&tv); + allocator->last_stamp = tv.tv_sec * 1000 + tv.tv_usec / 1000; /* milli seconds */ + allocator->stamp_counter = 0; + } + else + allocator->stamp_counter++; +} + +static inline ChunkLink* +magazine_chain_prepare_fields (ChunkLink *magazine_chunks) +{ + g_assert (MIN_MAGAZINE_SIZE >= 4); + /* ensure a magazine with at least 4 unused data pointers */ + ChunkLink *chunk1 = magazine_chain_pop_head (&magazine_chunks); + ChunkLink *chunk2 = magazine_chain_pop_head (&magazine_chunks); + ChunkLink *chunk3 = magazine_chain_pop_head (&magazine_chunks); + ChunkLink *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_next(mc) ((mc)->next->next->data) +#define magazine_chain_count(mc) ((mc)->next->next->next->data) + +static void +magazine_cache_trim (Allocator *allocator, + guint ix, + guint stamp) +{ + /* g_mutex_lock (allocator->mutex); done by caller */ + /* trim magazine cache from tail */ + ChunkLink *current = magazine_chain_prev (allocator->magazines[ix]); + ChunkLink *trash = NULL; + while (allocator->config.always_free || + ABS (stamp - (guint) magazine_chain_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; + } + g_mutex_unlock (allocator->magazine_mutex); + /* free trash */ + if (trash) + { + const guint chunk_size = SLAB_CHUNK_SIZE (allocator, ix); + g_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); + } + } + g_mutex_unlock (allocator->slab_mutex); + } +} + +static void +magazine_cache_push_magazine (guint ix, + ChunkLink *magazine_chunks, + gsize count) /* must be >= MIN_MAGAZINE_SIZE */ +{ + ChunkLink *current = magazine_chain_prepare_fields (magazine_chunks); + ChunkLink *next, *prev; + g_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) = (gpointer) count; + /* stamp magazine */ + magazine_cache_update_stamp(); + magazine_chain_stamp (current) = (gpointer) allocator->last_stamp; + allocator->magazines[ix] = current; + /* free old magazines beyond a certain threshold */ + magazine_cache_trim (allocator, ix, allocator->last_stamp); + /* g_mutex_unlock (allocator->mutex); was done by magazine_cache_trim() */ +} + +static ChunkLink* +magazine_cache_pop_magazine (guint ix, + gsize *countp) +{ + g_mutex_lock_a (allocator->magazine_mutex, &allocator->contention_counters[ix]); + if (!allocator->magazines[ix]) + { + guint magazine_threshold = allocator_get_magazine_threshold (allocator, ix); + gsize i, chunk_size = SLAB_CHUNK_SIZE (allocator, ix); + ChunkLink *current = NULL; + g_mutex_unlock (allocator->magazine_mutex); + g_mutex_lock (allocator->slab_mutex); + for (i = 0; i < magazine_threshold; i++) + { + ChunkLink *chunk = slab_allocator_alloc_chunk (chunk_size); + chunk->data = NULL; + chunk->next = current; + current = chunk; + } + g_mutex_unlock (allocator->slab_mutex); + *countp = i; + return current; + } + 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; + g_mutex_unlock (allocator->magazine_mutex); + /* clear special fields and hand out */ + *countp = (gsize) 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 (gpointer data) +{ + ThreadMemory *tmem = data; + const guint n_magazines = MAX_SLAB_INDEX (allocator); + guint ix; + for (ix = 0; ix < n_magazines; ix++) + { + Magazine *mags[2]; + guint 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 guint chunk_size = SLAB_CHUNK_SIZE (allocator, ix); + g_mutex_lock (allocator->slab_mutex); + while (mag->chunks) + { + ChunkLink *chunk = magazine_chain_pop_head (&mag->chunks); + slab_allocator_free_chunk (chunk_size, chunk); + } + g_mutex_unlock (allocator->slab_mutex); + } + } + } + g_free (tmem); +} + +static void +thread_memory_magazine1_reload (ThreadMemory *tmem, + guint ix) +{ + Magazine *mag = &tmem->magazine1[ix]; + g_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, + guint 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, + guint ix) +{ + Magazine xmag = tmem->magazine1[ix]; + tmem->magazine1[ix] = tmem->magazine2[ix]; + tmem->magazine2[ix] = xmag; +} + +static inline gboolean +thread_memory_magazine1_is_empty (ThreadMemory *tmem, + guint ix) +{ + return tmem->magazine1[ix].chunks == NULL; +} + +static inline gboolean +thread_memory_magazine2_is_full (ThreadMemory *tmem, + guint ix) +{ + return tmem->magazine2[ix].count >= allocator_get_magazine_threshold (allocator, ix); +} + +static inline gpointer +thread_memory_magazine1_alloc (ThreadMemory *tmem, + guint ix) +{ + Magazine *mag = &tmem->magazine1[ix]; + ChunkLink *chunk = magazine_chain_pop_head (&mag->chunks); + if (G_LIKELY (mag->count > 0)) + mag->count--; + return chunk; +} + +static inline void +thread_memory_magazine2_free (ThreadMemory *tmem, + guint ix, + gpointer mem) +{ + Magazine *mag = &tmem->magazine2[ix]; + ChunkLink *chunk = mem; + chunk->data = NULL; + chunk->next = mag->chunks; + mag->chunks = chunk; + mag->count++; +} + +/* --- API functions --- */ +gpointer +g_slice_alloc (gsize mem_size) +{ + gsize chunk_size; + gpointer mem; + guint acat; + chunk_size = P2ALIGN (mem_size); + acat = allocator_categorize (chunk_size); + if (G_LIKELY (acat == 1)) /* allocate through magazine layer */ + { + ThreadMemory *tmem = thread_memory_from_self(); + guint ix = SLAB_INDEX (allocator, chunk_size); + if (G_UNLIKELY (thread_memory_magazine1_is_empty (tmem, ix))) + { + thread_memory_swap_magazines (tmem, ix); + if (G_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 */ + { + g_mutex_lock (allocator->slab_mutex); + mem = slab_allocator_alloc_chunk (chunk_size); + g_mutex_unlock (allocator->slab_mutex); + } + else /* delegate to system malloc */ + mem = g_malloc (mem_size); + return mem; +} + +gpointer +g_slice_alloc0 (guint mem_size) +{ + gpointer mem = g_slice_alloc (mem_size); + if (mem) + memset (mem, 0, mem_size); + return mem; +} + +void +g_slice_free1 (guint mem_size, + gpointer mem_block) +{ + guint chunk_size = P2ALIGN (mem_size); + guint acat = allocator_categorize (chunk_size); + if (G_UNLIKELY (!mem_block)) + /* pass */; + else if (G_LIKELY (acat == 1)) /* allocate through magazine layer */ + { + ThreadMemory *tmem = thread_memory_from_self(); + guint ix = SLAB_INDEX (allocator, chunk_size); + if (G_UNLIKELY (thread_memory_magazine2_is_full (tmem, ix))) + { + thread_memory_swap_magazines (tmem, ix); + if (G_UNLIKELY (thread_memory_magazine2_is_full (tmem, ix))) + thread_memory_magazine2_unload (tmem, ix); + } + thread_memory_magazine2_free (tmem, ix, mem_block); + } + else if (acat == 2) /* allocate through slab allocator */ + { + g_mutex_lock (allocator->slab_mutex); + slab_allocator_free_chunk (chunk_size, mem_block); + g_mutex_unlock (allocator->slab_mutex); + } + else /* delegate to system malloc */ + g_free (mem_block); +} + +void +g_slice_free_chain (guint mem_size, + gpointer mem_chain, + guint next_offset) +{ + GSList *slice = mem_chain; + g_return_if_fail (next_offset == G_STRUCT_OFFSET (GSList, next)); + g_return_if_fail (mem_size >= sizeof (GSList)); + while (slice) + { + GSList *current = slice; + slice = slice->next; + g_slice_free1 (mem_size, current); + } + /* 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. + */ +} + +/* --- single page allocator --- */ +static void +allocator_slab_stack_push (Allocator *allocator, + guint 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 void +allocator_add_slab (Allocator *allocator, + guint ix, + guint chunk_size) +{ + SlabInfo *sinfo; + gsize padding, n_chunks, color = 0; + gsize page_size = SLAB_PAGE_SIZE (allocator, chunk_size); + /* allocate 1 page for the chunks and the slab */ + gpointer aligned_memory = allocator_memalign (page_size, page_size - NATIVE_MALLOC_PADDING); + guint8 *mem = aligned_memory; + if (!mem) + g_error ("%s: failed to allocate %lu bytes: %s", "GSlicedMemory", (gulong) (page_size - NATIVE_MALLOC_PADDING), g_strerror (errno)); + /* mask page adress */ + gsize addr = ((gsize) mem / page_size) * page_size; + /* assert alignment */ + g_assert (aligned_memory == (gpointer) 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 = ((guint8*) sinfo - mem) / chunk_size; + padding = ((guint8*) sinfo - mem) - n_chunks * chunk_size; + if (padding) + { + color = (allocator->color_accu * P2ALIGNMENT) % padding; + allocator->color_accu += 1; /* alternatively: + 0x7fffffff */ + } + /* add chunks to free list */ + ChunkLink *chunk = (ChunkLink*) (mem + color); + guint i; + sinfo->chunks = chunk; + for (i = 0; i < n_chunks - 1; i++) + { + chunk->next = (ChunkLink*) ((guint8*) chunk + chunk_size); + chunk = chunk->next; + } + chunk->next = NULL; /* last chunk */ + /* add slab to slab ring */ + allocator_slab_stack_push (allocator, ix, sinfo); +} + +static gpointer +slab_allocator_alloc_chunk (guint chunk_size) +{ + guint 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 */ + ChunkLink *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 (guint chunk_size, + gpointer mem) +{ + guint ix = SLAB_INDEX (allocator, chunk_size); + gsize page_size = SLAB_PAGE_SIZE (allocator, chunk_size); + gsize addr = ((gsize) mem / page_size) * page_size; + /* mask page adress */ + guint8 *page = (guint8*) addr; + SlabInfo *sinfo = (SlabInfo*) (page + page_size - SLAB_INFO_SIZE); + /* assert valid chunk count */ + g_assert (sinfo->n_allocated > 0); + /* add chunk to free list */ + gboolean was_empty = sinfo->chunks == NULL; + ChunkLink *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 --- */ +#include <malloc.h> /* memalign() */ + +/* from config.h: + * define HAVE_POSIX_MEMALIGN 1 // if free(posix_memalign(3)) works, <stdlib.h> + * define HAVE_MEMALIGN 1 // if free(memalign(3)) works, <malloc.h> + * define HAVE_VALLOC 1 // if free(valloc(3)) works, <stdlib.h> or <malloc.h> + * if none is provided, we implement malloc(3)-based alloc-only page alignment + */ + +#if !(HAVE_POSIX_MEMALIGN || HAVE_MEMALIGN || HAVE_VALLOC) +static GTrashStack *compat_valloc_trash = NULL; +#endif + +static gpointer +allocator_memalign (gsize alignment, + gsize memsize) +{ + gpointer aligned_memory = NULL; + gint 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 */ + g_assert (alignment == sys_page_size); + g_assert (memsize <= sys_page_size); + if (!compat_valloc_trash) + { + const guint n_pages = 16; + guint8 *mem = malloc (n_pages * sys_page_size); + err = errno; + if (mem) + { + gint i = n_pages; + guint8 *amem = (guint8*) ALIGN ((gsize) 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 (gsize memsize, + gpointer mem) +{ +#if HAVE_POSIX_MEMALIGN || HAVE_MEMALIGN || HAVE_VALLOC + free (mem); +#else + g_assert (memsize <= sys_page_size); + g_trash_stack_push (&compat_valloc_trash, mem); +#endif +} + +#define __G_SLICE_C__ +#include "galiasdef.c" diff --git a/glib/gslice.h b/glib/gslice.h new file mode 100644 index 000000000..dc0cc2e7f --- /dev/null +++ b/glib/gslice.h @@ -0,0 +1,70 @@ +/* GLIB sliced memory - fast threaded memory chunk allocator + * 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 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, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#ifndef __G_SLICE_H__ +#define __G_SLICE_H__ + +#ifndef __G_MEM_H__ +#error Include <glib.h> instead of <gslice.h> +#endif + +#include <glib/gtypes.h> + +G_BEGIN_DECLS + +/* slices - fast allocation/release of small memory blocks + */ +gpointer g_slice_alloc (gsize block_size) G_GNUC_MALLOC; +gpointer g_slice_alloc0 (gsize block_size) G_GNUC_MALLOC; +void g_slice_free1 (gsize block_size, + gpointer mem_block); +void g_slice_free_chain (gsize block_size, + gpointer mem_chain, + gsize next_offset); +#define g_slice_new(type) ((type*) g_slice_alloc (sizeof (type))) +#define g_slice_new0(type) ((type*) g_slice_alloc0 (sizeof (type))) +/* g_slice_free(type,mem) g_slice_free1 (sizeof (type), mem) */ + +#if __GNUC__ >= 2 +/* for GCC, define a type-safe variant of g_slice_free() */ +#define g_slice_free(type, mem) ({ \ + static inline void g_slice_free (gsize, type*); \ + while (0) g_slice_free (sizeof (type), mem); \ + g_slice_free1 (sizeof (type), mem); \ +}) +#else +#define g_slice_free(type, mem) g_slice_free1 (sizeof (type) + (gsize) (type*) 0, mem) +/* we go through the extra (gsize)(type*)0 hoop to ensure a known type argument */ +#endif + +/* --- internal debugging API --- */ +typedef enum { + G_SLICE_CONFIG_ALWAYS_MALLOC = 1, + G_SLICE_CONFIG_BYPASS_MAGAZINES, + G_SLICE_CONFIG_ALWAYS_FREE, + G_SLICE_CONFIG_WORKING_SET_MSECS, + G_SLICE_CONFIG_CHUNK_SIZES, + G_SLICE_CONFIG_CONTENTION_COUNTER, +} GSliceConfig; +void g_slice_set_config (GSliceConfig ckey, gint64 value); +gint64 g_slice_get_config (GSliceConfig ckey); +gint64* g_slice_get_config_state (GSliceConfig ckey, gint64 address, guint *n_values); + +G_END_DECLS + +#endif /* __G_SLICE_H__ */ diff --git a/glib/gthread.c b/glib/gthread.c index 1beca52ed..0dbfbc88d 100644 --- a/glib/gthread.c +++ b/glib/gthread.c @@ -76,7 +76,6 @@ struct _GRealThread { GThread thread; gpointer private_data; - gpointer mem_private; GRealThread *next; gpointer retval; GSystemThread system_thread; @@ -145,29 +144,34 @@ g_thread_init_glib (void) */ GRealThread* main_thread = (GRealThread*) g_thread_self (); + /* mutex and cond creation works without g_threads_got_initialized */ g_once_mutex = g_mutex_new (); g_once_cond = g_cond_new (); + /* we may only create mutex and cond in here */ + _g_mem_thread_init_noprivate_nomessage (); + + /* setup the basic threading system */ + g_threads_got_initialized = TRUE; + g_thread_specific_private = g_private_new (g_thread_cleanup); + g_private_set (g_thread_specific_private, main_thread); + G_THREAD_UF (thread_self, (&main_thread->system_thread)); + + /* complete memory system initialization, g_private_*() works now */ + _g_slice_thread_init_nomessage (); + + /* accomplish log system initialization to enable messaging */ + _g_messages_thread_init_nomessage (); + + /* we may run full-fledged initializers from here */ _g_convert_thread_init (); _g_rand_thread_init (); _g_main_thread_init (); - _g_mem_thread_init (); - _g_messages_thread_init (); _g_atomic_thread_init (); _g_utils_thread_init (); #ifdef G_OS_WIN32 _g_win32_thread_init (); #endif - - g_threads_got_initialized = TRUE; - - g_thread_specific_private = g_private_new (g_thread_cleanup); - g_private_set (g_thread_specific_private, main_thread); - G_THREAD_UF (thread_self, (&main_thread->system_thread)); - - _g_mem_thread_private_init (); - _g_messages_thread_private_init (); - } #endif /* G_THREADS_ENABLED */ @@ -868,28 +872,6 @@ g_static_rw_lock_free (GStaticRWLock* lock) g_static_mutex_free (&lock->mutex); } -/* - * Memory allocation can't use the regular GPrivate - * API, since that relies on GArray, which uses - * chunked memory. - */ -gpointer -_g_thread_mem_private_get (GThread *thread) -{ - GRealThread *real_thread = (GRealThread*) thread; - - return real_thread->mem_private; -} - -void -_g_thread_mem_private_set (GThread *thread, - gpointer data) -{ - GRealThread *real_thread = (GRealThread*) thread; - - real_thread->mem_private = data; -} - /** * g_thread_foreach * @thread_func: function to call for all GThread structures diff --git a/glib/gthread.h b/glib/gthread.h index 743981f7f..1234ec768 100644 --- a/glib/gthread.h +++ b/glib/gthread.h @@ -370,10 +370,6 @@ extern void glib_dummy_decl (void); # define G_TRYLOCK(name) (TRUE) #endif /* !G_THREADS_ENABLED */ -/* --- internal API --- */ -gpointer _g_thread_mem_private_get (GThread *thread); -void _g_thread_mem_private_set (GThread *thread, - gpointer data); G_END_DECLS diff --git a/glib/gthreadinit.h b/glib/gthreadinit.h index 5082cf852..6095de7af 100644 --- a/glib/gthreadinit.h +++ b/glib/gthreadinit.h @@ -26,9 +26,13 @@ G_BEGIN_DECLS /* Is called from gthread/gthread-impl.c */ void g_thread_init_glib (void); -/* Are called from glib/gthread.c. May not contain g_private_new calls */ -void _g_mem_thread_init (void) G_GNUC_INTERNAL; -void _g_messages_thread_init (void) G_GNUC_INTERNAL; +/* base initializers, may only use g_mutex_new(), g_cond_new() */ +void _g_mem_thread_init_noprivate_nomessage (void) G_GNUC_INTERNAL; +/* initializers that may also use g_private_new() */ +void _g_slice_thread_init_nomessage (void) G_GNUC_INTERNAL; +void _g_messages_thread_init_nomessage (void) G_GNUC_INTERNAL; + +/* full fledged initializersa */ void _g_convert_thread_init (void) G_GNUC_INTERNAL; void _g_rand_thread_init (void) G_GNUC_INTERNAL; void _g_main_thread_init (void) G_GNUC_INTERNAL; @@ -38,8 +42,10 @@ void _g_utils_thread_init (void) G_GNUC_INTERNAL; void _g_win32_thread_init (void) G_GNUC_INTERNAL; #endif -/* Are called from glib/gthread.c. Must only contain g_private_new calls */ -void _g_mem_thread_private_init (void) G_GNUC_INTERNAL; +/* initialization functions called from glib/gthread.c. + * may contain g_mutex_new(). + * may contain g_private_new() calls. + */ void _g_messages_thread_private_init (void) G_GNUC_INTERNAL; G_END_DECLS diff --git a/tests/Makefile.am b/tests/Makefile.am index 9424f5163..2ea56344e 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -91,6 +91,7 @@ test_programs = \ relation-test \ shell-test \ slist-test \ + slice-test \ spawn-test \ $(spawn_test_win32_gui) \ strfunc-test \ @@ -150,6 +151,7 @@ rand_test_LDADD = $(progs_ldadd) relation_test_LDADD = $(progs_ldadd) shell_test_LDADD = $(progs_ldadd) slist_test_LDADD = $(progs_ldadd) +slice_test_LDADD = $(thread_ldadd) spawn_test_LDADD = $(progs_ldadd) strfunc_test_LDADD = $(progs_ldadd) string_test_LDADD = $(progs_ldadd) diff --git a/tests/slice-test.c b/tests/slice-test.c new file mode 100644 index 000000000..fd7014332 --- /dev/null +++ b/tests/slice-test.c @@ -0,0 +1,168 @@ +/* GLIB sliced memory - fast threaded memory chunk allocator + * 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 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, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#include <glib.h> + +#include <stdio.h> +#include <string.h> +#include <sys/time.h> // gettimeofday + +#define quick_rand32() (rand_accu = 1664525 * rand_accu + 1013904223, rand_accu) +static guint prime_size = 1021; // 769; // 509 + +static gpointer +test_sliced_mem_thread (gpointer data) +{ + guint32 rand_accu = 2147483563; + /* initialize random numbers */ + if (data) + rand_accu = *(guint32*) data; + else + { + struct timeval rand_tv; + gettimeofday (&rand_tv, NULL); + rand_accu = rand_tv.tv_usec + (rand_tv.tv_sec << 16); + } + + guint i, m = 10000; /* number of blocks */ + guint j, n = 10000; /* number of alloc+free repetitions */ + guint8 **ps = g_new (guint8*, m); + guint *ss = g_new (guint, m); + /* create m random sizes */ + for (i = 0; i < m; i++) + ss[i] = quick_rand32() % prime_size; + /* allocate m blocks */ + for (i = 0; i < m; i++) + ps[i] = g_slice_alloc (ss[i]); + for (j = 0; j < n; j++) + { + /* free m/2 blocks */ + for (i = 0; i < m; i += 2) + g_slice_free1 (ss[i], ps[i]); + /* allocate m/2 blocks with new sizes */ + for (i = 0; i < m; i += 2) + { + ss[i] = quick_rand32() % prime_size; + ps[i] = g_slice_alloc (ss[i]); + } + } + /* free m blocks */ + for (i = 0; i < m; i++) + g_slice_free1 (ss[i], ps[i]); + /* alloc and free many equally sized chunks in a row */ + for (i = 0; i < n; i++) + { + guint sz = quick_rand32() % prime_size; + guint k = m / 100; + for (j = 0; j < k; j++) + ps[j] = g_slice_alloc (sz); + for (j = 0; j < k; j++) + g_slice_free1 (sz, ps[j]); + } + + return NULL; +} + +static void +usage (void) +{ + g_print ("Usage: gslicedmemory [n_threads] [G|S|M][f][c] [maxblocksize] [seed]\n"); +} + +int +main (int argc, + char *argv[]) +{ + guint seed32, *seedp = NULL; + gboolean ccounters = FALSE; + guint n_threads = 1; + const gchar *mode = "slab allocator + magazine cache", *emode = " "; + if (argc > 1) + n_threads = g_ascii_strtoull (argv[1], NULL, 10); + if (argc > 2) + { + guint i, l = strlen (argv[2]); + for (i = 0; i < l; i++) + switch (argv[2][i]) + { + case 'G': /* GLib mode */ + g_slice_set_config (G_SLICE_CONFIG_ALWAYS_MALLOC, FALSE); + g_slice_set_config (G_SLICE_CONFIG_BYPASS_MAGAZINES, FALSE); + mode = "slab allocator + magazine cache"; + break; + case 'S': /* slab mode */ + g_slice_set_config (G_SLICE_CONFIG_ALWAYS_MALLOC, FALSE); + g_slice_set_config (G_SLICE_CONFIG_BYPASS_MAGAZINES, TRUE); + mode = "slab allocator"; + break; + case 'M': /* malloc mode */ + g_slice_set_config (G_SLICE_CONFIG_ALWAYS_MALLOC, TRUE); + mode = "system malloc"; + break; + case 'f': /* eager freeing */ + g_slice_set_config (G_SLICE_CONFIG_ALWAYS_FREE, TRUE); + emode = " with eager freeing"; + break; + case 'c': /* print contention counters */ + ccounters = TRUE; + break; + default: + usage(); + return 1; + } + } + if (argc > 3) + prime_size = g_ascii_strtoull (argv[3], NULL, 10); + if (argc > 4) + { + seed32 = g_ascii_strtoull (argv[4], NULL, 10); + seedp = &seed32; + } + + g_thread_init (NULL); + + if (argc <= 1) + usage(); + + gchar strseed[64] = "<random>"; + if (seedp) + g_snprintf (strseed, 64, "%u", *seedp); + g_print ("Starting %d threads allocating random blocks <= %u bytes with seed=%s using %s%s\n", n_threads, prime_size, strseed, mode, emode); + + GThread *threads[n_threads]; + guint i; + for (i = 0; i < n_threads; i++) + threads[i] = g_thread_create_full (test_sliced_mem_thread, seedp, 0, TRUE, FALSE, 0, NULL); + for (i = 0; i < n_threads; i++) + g_thread_join (threads[i]); + + if (ccounters) + { + guint n, n_chunks = g_slice_get_config (G_SLICE_CONFIG_CHUNK_SIZES); + g_print (" ChunkSize | MagazineSize | Contention\n"); + for (i = 0; i < n_chunks; i++) + { + gint64 *vals = g_slice_get_config_state (G_SLICE_CONFIG_CONTENTION_COUNTER, i, &n); + g_print (" %9llu | %9llu | %9llu\n", vals[0], vals[2], vals[1]); + g_free (vals); + } + } + else + g_print ("Done.\n"); + return 0; +} |