nexmon – Blame information for rev 1
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | office | 1 | /* GLIB sliced memory - fast concurrent memory chunk allocator |
2 | * Copyright (C) 2005 Tim Janik |
||
3 | * |
||
4 | * This library is free software; you can redistribute it and/or |
||
5 | * modify it under the terms of the GNU Lesser General Public |
||
6 | * License as published by the Free Software Foundation; either |
||
7 | * version 2 of the License, or (at your option) any later version. |
||
8 | * |
||
9 | * This library is distributed in the hope that it will be useful, |
||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
||
12 | * Lesser General Public License for more details. |
||
13 | * |
||
14 | * You should have received a copy of the GNU Lesser General Public |
||
15 | * License along with this library; if not, see <http://www.gnu.org/licenses/>. |
||
16 | */ |
||
17 | /* MT safe */ |
||
18 | |||
19 | #include "config.h" |
||
20 | #include "glibconfig.h" |
||
21 | |||
22 | #if defined HAVE_POSIX_MEMALIGN && defined POSIX_MEMALIGN_WITH_COMPLIANT_ALLOCS |
||
23 | # define HAVE_COMPLIANT_POSIX_MEMALIGN 1 |
||
24 | #endif |
||
25 | |||
26 | #if defined(HAVE_COMPLIANT_POSIX_MEMALIGN) && !defined(_XOPEN_SOURCE) |
||
27 | #define _XOPEN_SOURCE 600 /* posix_memalign() */ |
||
28 | #endif |
||
29 | #include <stdlib.h> /* posix_memalign() */ |
||
30 | #include <string.h> |
||
31 | #include <errno.h> |
||
32 | |||
33 | #ifdef G_OS_UNIX |
||
34 | #include <unistd.h> /* sysconf() */ |
||
35 | #endif |
||
36 | #ifdef G_OS_WIN32 |
||
37 | #include <windows.h> |
||
38 | #include <process.h> |
||
39 | #endif |
||
40 | |||
41 | #include <stdio.h> /* fputs/fprintf */ |
||
42 | |||
43 | #include "gslice.h" |
||
44 | |||
45 | #include "gmain.h" |
||
46 | #include "gmem.h" /* gslice.h */ |
||
47 | #include "gstrfuncs.h" |
||
48 | #include "gutils.h" |
||
49 | #include "gtrashstack.h" |
||
50 | #include "gtestutils.h" |
||
51 | #include "gthread.h" |
||
52 | #include "glib_trace.h" |
||
53 | |||
54 | #include "valgrind.h" |
||
55 | |||
56 | /** |
||
57 | * SECTION:memory_slices |
||
58 | * @title: Memory Slices |
||
59 | * @short_description: efficient way to allocate groups of equal-sized |
||
60 | * chunks of memory |
||
61 | * |
||
62 | * Memory slices provide a space-efficient and multi-processing scalable |
||
63 | * way to allocate equal-sized pieces of memory, just like the original |
||
64 | * #GMemChunks (from GLib 2.8), while avoiding their excessive |
||
65 | * memory-waste, scalability and performance problems. |
||
66 | * |
||
67 | * To achieve these goals, the slice allocator uses a sophisticated, |
||
68 | * layered design that has been inspired by Bonwick's slab allocator |
||
69 | * ([Bonwick94](http://citeseer.ist.psu.edu/bonwick94slab.html) |
||
70 | * Jeff Bonwick, The slab allocator: An object-caching kernel |
||
71 | * memory allocator. USENIX 1994, and |
||
72 | * [Bonwick01](http://citeseer.ist.psu.edu/bonwick01magazines.html) |
||
73 | * Bonwick and Jonathan Adams, Magazines and vmem: Extending the |
||
74 | * slab allocator to many cpu's and arbitrary resources. USENIX 2001) |
||
75 | * |
||
76 | * It uses posix_memalign() to optimize allocations of many equally-sized |
||
77 | * chunks, and has per-thread free lists (the so-called magazine layer) |
||
78 | * to quickly satisfy allocation requests of already known structure sizes. |
||
79 | * This is accompanied by extra caching logic to keep freed memory around |
||
80 | * for some time before returning it to the system. Memory that is unused |
||
81 | * due to alignment constraints is used for cache colorization (random |
||
82 | * distribution of chunk addresses) to improve CPU cache utilization. The |
||
83 | * caching layer of the slice allocator adapts itself to high lock contention |
||
84 | * to improve scalability. |
||
85 | * |
||
86 | * The slice allocator can allocate blocks as small as two pointers, and |
||
87 | * unlike malloc(), it does not reserve extra space per block. For large block |
||
88 | * sizes, g_slice_new() and g_slice_alloc() will automatically delegate to the |
||
89 | * system malloc() implementation. For newly written code it is recommended |
||
90 | * to use the new `g_slice` API instead of g_malloc() and |
||
91 | * friends, as long as objects are not resized during their lifetime and the |
||
92 | * object size used at allocation time is still available when freeing. |
||
93 | * |
||
94 | * Here is an example for using the slice allocator: |
||
95 | * |[<!-- language="C" --> |
||
96 | * gchar *mem[10000]; |
||
97 | * gint i; |
||
98 | * |
||
99 | * // Allocate 10000 blocks. |
||
100 | * for (i = 0; i < 10000; i++) |
||
101 | * { |
||
102 | * mem[i] = g_slice_alloc (50); |
||
103 | * |
||
104 | * // Fill in the memory with some junk. |
||
105 | * for (j = 0; j < 50; j++) |
||
106 | * mem[i][j] = i * j; |
||
107 | * } |
||
108 | * |
||
109 | * // Now free all of the blocks. |
||
110 | * for (i = 0; i < 10000; i++) |
||
111 | * g_slice_free1 (50, mem[i]); |
||
112 | * ]| |
||
113 | * |
||
114 | * And here is an example for using the using the slice allocator |
||
115 | * with data structures: |
||
116 | * |[<!-- language="C" --> |
||
117 | * GRealArray *array; |
||
118 | * |
||
119 | * // Allocate one block, using the g_slice_new() macro. |
||
120 | * array = g_slice_new (GRealArray); |
||
121 | |||
122 | * // We can now use array just like a normal pointer to a structure. |
||
123 | * array->data = NULL; |
||
124 | * array->len = 0; |
||
125 | * array->alloc = 0; |
||
126 | * array->zero_terminated = (zero_terminated ? 1 : 0); |
||
127 | * array->clear = (clear ? 1 : 0); |
||
128 | * array->elt_size = elt_size; |
||
129 | * |
||
130 | * // We can free the block, so it can be reused. |
||
131 | * g_slice_free (GRealArray, array); |
||
132 | * ]| |
||
133 | */ |
||
134 | |||
135 | /* the GSlice allocator is split up into 4 layers, roughly modelled after the slab |
||
136 | * allocator and magazine extensions as outlined in: |
||
137 | * + [Bonwick94] Jeff Bonwick, The slab allocator: An object-caching kernel |
||
138 | * memory allocator. USENIX 1994, http://citeseer.ist.psu.edu/bonwick94slab.html |
||
139 | * + [Bonwick01] Bonwick and Jonathan Adams, Magazines and vmem: Extending the |
||
140 | * slab allocator to many cpu's and arbitrary resources. |
||
141 | * USENIX 2001, http://citeseer.ist.psu.edu/bonwick01magazines.html |
||
142 | * the layers are: |
||
143 | * - the thread magazines. for each (aligned) chunk size, a magazine (a list) |
||
144 | * of recently freed and soon to be allocated chunks is maintained per thread. |
||
145 | * this way, most alloc/free requests can be quickly satisfied from per-thread |
||
146 | * free lists which only require one g_private_get() call to retrive the |
||
147 | * thread handle. |
||
148 | * - the magazine cache. allocating and freeing chunks to/from threads only |
||
149 | * occours at magazine sizes from a global depot of magazines. the depot |
||
150 | * maintaines a 15 second working set of allocated magazines, so full |
||
151 | * magazines are not allocated and released too often. |
||
152 | * the chunk size dependent magazine sizes automatically adapt (within limits, |
||
153 | * see [3]) to lock contention to properly scale performance across a variety |
||
154 | * of SMP systems. |
||
155 | * - the slab allocator. this allocator allocates slabs (blocks of memory) close |
||
156 | * to the system page size or multiples thereof which have to be page aligned. |
||
157 | * the blocks are divided into smaller chunks which are used to satisfy |
||
158 | * allocations from the upper layers. the space provided by the reminder of |
||
159 | * the chunk size division is used for cache colorization (random distribution |
||
160 | * of chunk addresses) to improve processor cache utilization. multiple slabs |
||
161 | * with the same chunk size are kept in a partially sorted ring to allow O(1) |
||
162 | * freeing and allocation of chunks (as long as the allocation of an entirely |
||
163 | * new slab can be avoided). |
||
164 | * - the page allocator. on most modern systems, posix_memalign(3) or |
||
165 | * memalign(3) should be available, so this is used to allocate blocks with |
||
166 | * system page size based alignments and sizes or multiples thereof. |
||
167 | * if no memalign variant is provided, valloc() is used instead and |
||
168 | * block sizes are limited to the system page size (no multiples thereof). |
||
169 | * as a fallback, on system without even valloc(), a malloc(3)-based page |
||
170 | * allocator with alloc-only behaviour is used. |
||
171 | * |
||
172 | * NOTES: |
||
173 | * [1] some systems memalign(3) implementations may rely on boundary tagging for |
||
174 | * the handed out memory chunks. to avoid excessive page-wise fragmentation, |
||
175 | * we reserve 2 * sizeof (void*) per block size for the systems memalign(3), |
||
176 | * specified in NATIVE_MALLOC_PADDING. |
||
177 | * [2] using the slab allocator alone already provides for a fast and efficient |
||
178 | * allocator, it doesn't properly scale beyond single-threaded uses though. |
||
179 | * also, the slab allocator implements eager free(3)-ing, i.e. does not |
||
180 | * provide any form of caching or working set maintenance. so if used alone, |
||
181 | * it's vulnerable to trashing for sequences of balanced (alloc, free) pairs |
||
182 | * at certain thresholds. |
||
183 | * [3] magazine sizes are bound by an implementation specific minimum size and |
||
184 | * a chunk size specific maximum to limit magazine storage sizes to roughly |
||
185 | * 16KB. |
||
186 | * [4] allocating ca. 8 chunks per block/page keeps a good balance between |
||
187 | * external and internal fragmentation (<= 12.5%). [Bonwick94] |
||
188 | */ |
||
189 | |||
190 | /* --- macros and constants --- */ |
||
191 | #define LARGEALIGNMENT (256) |
||
192 | #define P2ALIGNMENT (2 * sizeof (gsize)) /* fits 2 pointers (assumed to be 2 * GLIB_SIZEOF_SIZE_T below) */ |
||
193 | #define ALIGN(size, base) ((base) * (gsize) (((size) + (base) - 1) / (base))) |
||
194 | #define NATIVE_MALLOC_PADDING P2ALIGNMENT /* per-page padding left for native malloc(3) see [1] */ |
||
195 | #define SLAB_INFO_SIZE P2ALIGN (sizeof (SlabInfo) + NATIVE_MALLOC_PADDING) |
||
196 | #define MAX_MAGAZINE_SIZE (256) /* see [3] and allocator_get_magazine_threshold() for this */ |
||
197 | #define MIN_MAGAZINE_SIZE (4) |
||
198 | #define MAX_STAMP_COUNTER (7) /* distributes the load of gettimeofday() */ |
||
199 | #define MAX_SLAB_CHUNK_SIZE(al) (((al)->max_page_size - SLAB_INFO_SIZE) / 8) /* we want at last 8 chunks per page, see [4] */ |
||
200 | #define MAX_SLAB_INDEX(al) (SLAB_INDEX (al, MAX_SLAB_CHUNK_SIZE (al)) + 1) |
||
201 | #define SLAB_INDEX(al, asize) ((asize) / P2ALIGNMENT - 1) /* asize must be P2ALIGNMENT aligned */ |
||
202 | #define SLAB_CHUNK_SIZE(al, ix) (((ix) + 1) * P2ALIGNMENT) |
||
203 | #define SLAB_BPAGE_SIZE(al,csz) (8 * (csz) + SLAB_INFO_SIZE) |
||
204 | |||
205 | /* optimized version of ALIGN (size, P2ALIGNMENT) */ |
||
206 | #if GLIB_SIZEOF_SIZE_T * 2 == 8 /* P2ALIGNMENT */ |
||
207 | #define P2ALIGN(size) (((size) + 0x7) & ~(gsize) 0x7) |
||
208 | #elif GLIB_SIZEOF_SIZE_T * 2 == 16 /* P2ALIGNMENT */ |
||
209 | #define P2ALIGN(size) (((size) + 0xf) & ~(gsize) 0xf) |
||
210 | #else |
||
211 | #define P2ALIGN(size) ALIGN (size, P2ALIGNMENT) |
||
212 | #endif |
||
213 | |||
214 | /* special helpers to avoid gmessage.c dependency */ |
||
215 | static void mem_error (const char *format, ...) G_GNUC_PRINTF (1,2); |
||
216 | #define mem_assert(cond) do { if (G_LIKELY (cond)) ; else mem_error ("assertion failed: %s", #cond); } while (0) |
||
217 | |||
218 | /* --- structures --- */ |
||
219 | typedef struct _ChunkLink ChunkLink; |
||
220 | typedef struct _SlabInfo SlabInfo; |
||
221 | typedef struct _CachedMagazine CachedMagazine; |
||
222 | struct _ChunkLink { |
||
223 | ChunkLink *next; |
||
224 | ChunkLink *data; |
||
225 | }; |
||
226 | struct _SlabInfo { |
||
227 | ChunkLink *chunks; |
||
228 | guint n_allocated; |
||
229 | SlabInfo *next, *prev; |
||
230 | }; |
||
231 | typedef struct { |
||
232 | ChunkLink *chunks; |
||
233 | gsize count; /* approximative chunks list length */ |
||
234 | } Magazine; |
||
235 | typedef struct { |
||
236 | Magazine *magazine1; /* array of MAX_SLAB_INDEX (allocator) */ |
||
237 | Magazine *magazine2; /* array of MAX_SLAB_INDEX (allocator) */ |
||
238 | } ThreadMemory; |
||
239 | typedef struct { |
||
240 | gboolean always_malloc; |
||
241 | gboolean bypass_magazines; |
||
242 | gboolean debug_blocks; |
||
243 | gsize working_set_msecs; |
||
244 | guint color_increment; |
||
245 | } SliceConfig; |
||
246 | typedef struct { |
||
247 | /* const after initialization */ |
||
248 | gsize min_page_size, max_page_size; |
||
249 | SliceConfig config; |
||
250 | gsize max_slab_chunk_size_for_magazine_cache; |
||
251 | /* magazine cache */ |
||
252 | GMutex magazine_mutex; |
||
253 | ChunkLink **magazines; /* array of MAX_SLAB_INDEX (allocator) */ |
||
254 | guint *contention_counters; /* array of MAX_SLAB_INDEX (allocator) */ |
||
255 | gint mutex_counter; |
||
256 | guint stamp_counter; |
||
257 | guint last_stamp; |
||
258 | /* slab allocator */ |
||
259 | GMutex slab_mutex; |
||
260 | SlabInfo **slab_stack; /* array of MAX_SLAB_INDEX (allocator) */ |
||
261 | guint color_accu; |
||
262 | } Allocator; |
||
263 | |||
264 | /* --- g-slice prototypes --- */ |
||
265 | static gpointer slab_allocator_alloc_chunk (gsize chunk_size); |
||
266 | static void slab_allocator_free_chunk (gsize chunk_size, |
||
267 | gpointer mem); |
||
268 | static void private_thread_memory_cleanup (gpointer data); |
||
269 | static gpointer allocator_memalign (gsize alignment, |
||
270 | gsize memsize); |
||
271 | static void allocator_memfree (gsize memsize, |
||
272 | gpointer mem); |
||
273 | static inline void magazine_cache_update_stamp (void); |
||
274 | static inline gsize allocator_get_magazine_threshold (Allocator *allocator, |
||
275 | guint ix); |
||
276 | |||
277 | /* --- g-slice memory checker --- */ |
||
278 | static void smc_notify_alloc (void *pointer, |
||
279 | size_t size); |
||
280 | static int smc_notify_free (void *pointer, |
||
281 | size_t size); |
||
282 | |||
283 | /* --- variables --- */ |
||
284 | static GPrivate private_thread_memory = G_PRIVATE_INIT (private_thread_memory_cleanup); |
||
285 | static gsize sys_page_size = 0; |
||
286 | static Allocator allocator[1] = { { 0, }, }; |
||
287 | static SliceConfig slice_config = { |
||
288 | FALSE, /* always_malloc */ |
||
289 | FALSE, /* bypass_magazines */ |
||
290 | FALSE, /* debug_blocks */ |
||
291 | 15 * 1000, /* working_set_msecs */ |
||
292 | 1, /* color increment, alt: 0x7fffffff */ |
||
293 | }; |
||
294 | static GMutex smc_tree_mutex; /* mutex for G_SLICE=debug-blocks */ |
||
295 | |||
296 | /* --- auxiliary funcitons --- */ |
||
297 | void |
||
298 | g_slice_set_config (GSliceConfig ckey, |
||
299 | gint64 value) |
||
300 | { |
||
301 | g_return_if_fail (sys_page_size == 0); |
||
302 | switch (ckey) |
||
303 | { |
||
304 | case G_SLICE_CONFIG_ALWAYS_MALLOC: |
||
305 | slice_config.always_malloc = value != 0; |
||
306 | break; |
||
307 | case G_SLICE_CONFIG_BYPASS_MAGAZINES: |
||
308 | slice_config.bypass_magazines = value != 0; |
||
309 | break; |
||
310 | case G_SLICE_CONFIG_WORKING_SET_MSECS: |
||
311 | slice_config.working_set_msecs = value; |
||
312 | break; |
||
313 | case G_SLICE_CONFIG_COLOR_INCREMENT: |
||
314 | slice_config.color_increment = value; |
||
315 | default: ; |
||
316 | } |
||
317 | } |
||
318 | |||
319 | gint64 |
||
320 | g_slice_get_config (GSliceConfig ckey) |
||
321 | { |
||
322 | switch (ckey) |
||
323 | { |
||
324 | case G_SLICE_CONFIG_ALWAYS_MALLOC: |
||
325 | return slice_config.always_malloc; |
||
326 | case G_SLICE_CONFIG_BYPASS_MAGAZINES: |
||
327 | return slice_config.bypass_magazines; |
||
328 | case G_SLICE_CONFIG_WORKING_SET_MSECS: |
||
329 | return slice_config.working_set_msecs; |
||
330 | case G_SLICE_CONFIG_CHUNK_SIZES: |
||
331 | return MAX_SLAB_INDEX (allocator); |
||
332 | case G_SLICE_CONFIG_COLOR_INCREMENT: |
||
333 | return slice_config.color_increment; |
||
334 | default: |
||
335 | return 0; |
||
336 | } |
||
337 | } |
||
338 | |||
339 | gint64* |
||
340 | g_slice_get_config_state (GSliceConfig ckey, |
||
341 | gint64 address, |
||
342 | guint *n_values) |
||
343 | { |
||
344 | guint i = 0; |
||
345 | g_return_val_if_fail (n_values != NULL, NULL); |
||
346 | *n_values = 0; |
||
347 | switch (ckey) |
||
348 | { |
||
349 | gint64 array[64]; |
||
350 | case G_SLICE_CONFIG_CONTENTION_COUNTER: |
||
351 | array[i++] = SLAB_CHUNK_SIZE (allocator, address); |
||
352 | array[i++] = allocator->contention_counters[address]; |
||
353 | array[i++] = allocator_get_magazine_threshold (allocator, address); |
||
354 | *n_values = i; |
||
355 | return g_memdup (array, sizeof (array[0]) * *n_values); |
||
356 | default: |
||
357 | return NULL; |
||
358 | } |
||
359 | } |
||
360 | |||
361 | static void |
||
362 | slice_config_init (SliceConfig *config) |
||
363 | { |
||
364 | const gchar *val; |
||
365 | |||
366 | *config = slice_config; |
||
367 | |||
368 | val = getenv ("G_SLICE"); |
||
369 | if (val != NULL) |
||
370 | { |
||
371 | gint flags; |
||
372 | const GDebugKey keys[] = { |
||
373 | { "always-malloc", 1 << 0 }, |
||
374 | { "debug-blocks", 1 << 1 }, |
||
375 | }; |
||
376 | |||
377 | flags = g_parse_debug_string (val, keys, G_N_ELEMENTS (keys)); |
||
378 | if (flags & (1 << 0)) |
||
379 | config->always_malloc = TRUE; |
||
380 | if (flags & (1 << 1)) |
||
381 | config->debug_blocks = TRUE; |
||
382 | } |
||
383 | else |
||
384 | { |
||
385 | /* G_SLICE was not specified, so check if valgrind is running and |
||
386 | * disable ourselves if it is. |
||
387 | * |
||
388 | * This way it's possible to force gslice to be enabled under |
||
389 | * valgrind just by setting G_SLICE to the empty string. |
||
390 | */ |
||
391 | if (RUNNING_ON_VALGRIND) |
||
392 | config->always_malloc = TRUE; |
||
393 | } |
||
394 | } |
||
395 | |||
396 | static void |
||
397 | g_slice_init_nomessage (void) |
||
398 | { |
||
399 | /* we may not use g_error() or friends here */ |
||
400 | mem_assert (sys_page_size == 0); |
||
401 | mem_assert (MIN_MAGAZINE_SIZE >= 4); |
||
402 | |||
403 | #ifdef G_OS_WIN32 |
||
404 | { |
||
405 | SYSTEM_INFO system_info; |
||
406 | GetSystemInfo (&system_info); |
||
407 | sys_page_size = system_info.dwPageSize; |
||
408 | } |
||
409 | #else |
||
410 | sys_page_size = sysconf (_SC_PAGESIZE); /* = sysconf (_SC_PAGE_SIZE); = getpagesize(); */ |
||
411 | #endif |
||
412 | mem_assert (sys_page_size >= 2 * LARGEALIGNMENT); |
||
413 | mem_assert ((sys_page_size & (sys_page_size - 1)) == 0); |
||
414 | slice_config_init (&allocator->config); |
||
415 | allocator->min_page_size = sys_page_size; |
||
416 | #if HAVE_COMPLIANT_POSIX_MEMALIGN || HAVE_MEMALIGN |
||
417 | /* allow allocation of pages up to 8KB (with 8KB alignment). |
||
418 | * this is useful because many medium to large sized structures |
||
419 | * fit less than 8 times (see [4]) into 4KB pages. |
||
420 | * we allow very small page sizes here, to reduce wastage in |
||
421 | * threads if only small allocations are required (this does |
||
422 | * bear the risk of increasing allocation times and fragmentation |
||
423 | * though). |
||
424 | */ |
||
425 | allocator->min_page_size = MAX (allocator->min_page_size, 4096); |
||
426 | allocator->max_page_size = MAX (allocator->min_page_size, 8192); |
||
427 | allocator->min_page_size = MIN (allocator->min_page_size, 128); |
||
428 | #else |
||
429 | /* we can only align to system page size */ |
||
430 | allocator->max_page_size = sys_page_size; |
||
431 | #endif |
||
432 | if (allocator->config.always_malloc) |
||
433 | { |
||
434 | allocator->contention_counters = NULL; |
||
435 | allocator->magazines = NULL; |
||
436 | allocator->slab_stack = NULL; |
||
437 | } |
||
438 | else |
||
439 | { |
||
440 | allocator->contention_counters = g_new0 (guint, MAX_SLAB_INDEX (allocator)); |
||
441 | allocator->magazines = g_new0 (ChunkLink*, MAX_SLAB_INDEX (allocator)); |
||
442 | allocator->slab_stack = g_new0 (SlabInfo*, MAX_SLAB_INDEX (allocator)); |
||
443 | } |
||
444 | |||
445 | allocator->mutex_counter = 0; |
||
446 | allocator->stamp_counter = MAX_STAMP_COUNTER; /* force initial update */ |
||
447 | allocator->last_stamp = 0; |
||
448 | allocator->color_accu = 0; |
||
449 | magazine_cache_update_stamp(); |
||
450 | /* values cached for performance reasons */ |
||
451 | allocator->max_slab_chunk_size_for_magazine_cache = MAX_SLAB_CHUNK_SIZE (allocator); |
||
452 | if (allocator->config.always_malloc || allocator->config.bypass_magazines) |
||
453 | allocator->max_slab_chunk_size_for_magazine_cache = 0; /* non-optimized cases */ |
||
454 | } |
||
455 | |||
456 | static inline guint |
||
457 | allocator_categorize (gsize aligned_chunk_size) |
||
458 | { |
||
459 | /* speed up the likely path */ |
||
460 | if (G_LIKELY (aligned_chunk_size && aligned_chunk_size <= allocator->max_slab_chunk_size_for_magazine_cache)) |
||
461 | return 1; /* use magazine cache */ |
||
462 | |||
463 | if (!allocator->config.always_malloc && |
||
464 | aligned_chunk_size && |
||
465 | aligned_chunk_size <= MAX_SLAB_CHUNK_SIZE (allocator)) |
||
466 | { |
||
467 | if (allocator->config.bypass_magazines) |
||
468 | return 2; /* use slab allocator, see [2] */ |
||
469 | return 1; /* use magazine cache */ |
||
470 | } |
||
471 | return 0; /* use malloc() */ |
||
472 | } |
||
473 | |||
474 | static inline void |
||
475 | g_mutex_lock_a (GMutex *mutex, |
||
476 | guint *contention_counter) |
||
477 | { |
||
478 | gboolean contention = FALSE; |
||
479 | if (!g_mutex_trylock (mutex)) |
||
480 | { |
||
481 | g_mutex_lock (mutex); |
||
482 | contention = TRUE; |
||
483 | } |
||
484 | if (contention) |
||
485 | { |
||
486 | allocator->mutex_counter++; |
||
487 | if (allocator->mutex_counter >= 1) /* quickly adapt to contention */ |
||
488 | { |
||
489 | allocator->mutex_counter = 0; |
||
490 | *contention_counter = MIN (*contention_counter + 1, MAX_MAGAZINE_SIZE); |
||
491 | } |
||
492 | } |
||
493 | else /* !contention */ |
||
494 | { |
||
495 | allocator->mutex_counter--; |
||
496 | if (allocator->mutex_counter < -11) /* moderately recover magazine sizes */ |
||
497 | { |
||
498 | allocator->mutex_counter = 0; |
||
499 | *contention_counter = MAX (*contention_counter, 1) - 1; |
||
500 | } |
||
501 | } |
||
502 | } |
||
503 | |||
504 | static inline ThreadMemory* |
||
505 | thread_memory_from_self (void) |
||
506 | { |
||
507 | ThreadMemory *tmem = g_private_get (&private_thread_memory); |
||
508 | if (G_UNLIKELY (!tmem)) |
||
509 | { |
||
510 | static GMutex init_mutex; |
||
511 | guint n_magazines; |
||
512 | |||
513 | g_mutex_lock (&init_mutex); |
||
514 | if G_UNLIKELY (sys_page_size == 0) |
||
515 | g_slice_init_nomessage (); |
||
516 | g_mutex_unlock (&init_mutex); |
||
517 | |||
518 | n_magazines = MAX_SLAB_INDEX (allocator); |
||
519 | tmem = g_malloc0 (sizeof (ThreadMemory) + sizeof (Magazine) * 2 * n_magazines); |
||
520 | tmem->magazine1 = (Magazine*) (tmem + 1); |
||
521 | tmem->magazine2 = &tmem->magazine1[n_magazines]; |
||
522 | g_private_set (&private_thread_memory, tmem); |
||
523 | } |
||
524 | return tmem; |
||
525 | } |
||
526 | |||
527 | static inline ChunkLink* |
||
528 | magazine_chain_pop_head (ChunkLink **magazine_chunks) |
||
529 | { |
||
530 | /* magazine chains are linked via ChunkLink->next. |
||
531 | * each ChunkLink->data of the toplevel chain may point to a subchain, |
||
532 | * linked via ChunkLink->next. ChunkLink->data of the subchains just |
||
533 | * contains uninitialized junk. |
||
534 | */ |
||
535 | ChunkLink *chunk = (*magazine_chunks)->data; |
||
536 | if (G_UNLIKELY (chunk)) |
||
537 | { |
||
538 | /* allocating from freed list */ |
||
539 | (*magazine_chunks)->data = chunk->next; |
||
540 | } |
||
541 | else |
||
542 | { |
||
543 | chunk = *magazine_chunks; |
||
544 | *magazine_chunks = chunk->next; |
||
545 | } |
||
546 | return chunk; |
||
547 | } |
||
548 | |||
549 | #if 0 /* useful for debugging */ |
||
550 | static guint |
||
551 | magazine_count (ChunkLink *head) |
||
552 | { |
||
553 | guint count = 0; |
||
554 | if (!head) |
||
555 | return 0; |
||
556 | while (head) |
||
557 | { |
||
558 | ChunkLink *child = head->data; |
||
559 | count += 1; |
||
560 | for (child = head->data; child; child = child->next) |
||
561 | count += 1; |
||
562 | head = head->next; |
||
563 | } |
||
564 | return count; |
||
565 | } |
||
566 | #endif |
||
567 | |||
568 | static inline gsize |
||
569 | allocator_get_magazine_threshold (Allocator *allocator, |
||
570 | guint ix) |
||
571 | { |
||
572 | /* the magazine size calculated here has a lower bound of MIN_MAGAZINE_SIZE, |
||
573 | * which is required by the implementation. also, for moderately sized chunks |
||
574 | * (say >= 64 bytes), magazine sizes shouldn't be much smaller then the number |
||
575 | * of chunks available per page/2 to avoid excessive traffic in the magazine |
||
576 | * cache for small to medium sized structures. |
||
577 | * the upper bound of the magazine size is effectively provided by |
||
578 | * MAX_MAGAZINE_SIZE. for larger chunks, this number is scaled down so that |
||
579 | * the content of a single magazine doesn't exceed ca. 16KB. |
||
580 | */ |
||
581 | gsize chunk_size = SLAB_CHUNK_SIZE (allocator, ix); |
||
582 | guint threshold = MAX (MIN_MAGAZINE_SIZE, allocator->max_page_size / MAX (5 * chunk_size, 5 * 32)); |
||
583 | guint contention_counter = allocator->contention_counters[ix]; |
||
584 | if (G_UNLIKELY (contention_counter)) /* single CPU bias */ |
||
585 | { |
||
586 | /* adapt contention counter thresholds to chunk sizes */ |
||
587 | contention_counter = contention_counter * 64 / chunk_size; |
||
588 | threshold = MAX (threshold, contention_counter); |
||
589 | } |
||
590 | return threshold; |
||
591 | } |
||
592 | |||
593 | /* --- magazine cache --- */ |
||
594 | static inline void |
||
595 | magazine_cache_update_stamp (void) |
||
596 | { |
||
597 | if (allocator->stamp_counter >= MAX_STAMP_COUNTER) |
||
598 | { |
||
599 | GTimeVal tv; |
||
600 | g_get_current_time (&tv); |
||
601 | allocator->last_stamp = tv.tv_sec * 1000 + tv.tv_usec / 1000; /* milli seconds */ |
||
602 | allocator->stamp_counter = 0; |
||
603 | } |
||
604 | else |
||
605 | allocator->stamp_counter++; |
||
606 | } |
||
607 | |||
608 | static inline ChunkLink* |
||
609 | magazine_chain_prepare_fields (ChunkLink *magazine_chunks) |
||
610 | { |
||
611 | ChunkLink *chunk1; |
||
612 | ChunkLink *chunk2; |
||
613 | ChunkLink *chunk3; |
||
614 | ChunkLink *chunk4; |
||
615 | /* checked upon initialization: mem_assert (MIN_MAGAZINE_SIZE >= 4); */ |
||
616 | /* ensure a magazine with at least 4 unused data pointers */ |
||
617 | chunk1 = magazine_chain_pop_head (&magazine_chunks); |
||
618 | chunk2 = magazine_chain_pop_head (&magazine_chunks); |
||
619 | chunk3 = magazine_chain_pop_head (&magazine_chunks); |
||
620 | chunk4 = magazine_chain_pop_head (&magazine_chunks); |
||
621 | chunk4->next = magazine_chunks; |
||
622 | chunk3->next = chunk4; |
||
623 | chunk2->next = chunk3; |
||
624 | chunk1->next = chunk2; |
||
625 | return chunk1; |
||
626 | } |
||
627 | |||
628 | /* access the first 3 fields of a specially prepared magazine chain */ |
||
629 | #define magazine_chain_prev(mc) ((mc)->data) |
||
630 | #define magazine_chain_stamp(mc) ((mc)->next->data) |
||
631 | #define magazine_chain_uint_stamp(mc) GPOINTER_TO_UINT ((mc)->next->data) |
||
632 | #define magazine_chain_next(mc) ((mc)->next->next->data) |
||
633 | #define magazine_chain_count(mc) ((mc)->next->next->next->data) |
||
634 | |||
635 | static void |
||
636 | magazine_cache_trim (Allocator *allocator, |
||
637 | guint ix, |
||
638 | guint stamp) |
||
639 | { |
||
640 | /* g_mutex_lock (allocator->mutex); done by caller */ |
||
641 | /* trim magazine cache from tail */ |
||
642 | ChunkLink *current = magazine_chain_prev (allocator->magazines[ix]); |
||
643 | ChunkLink *trash = NULL; |
||
644 | while (ABS (stamp - magazine_chain_uint_stamp (current)) >= allocator->config.working_set_msecs) |
||
645 | { |
||
646 | /* unlink */ |
||
647 | ChunkLink *prev = magazine_chain_prev (current); |
||
648 | ChunkLink *next = magazine_chain_next (current); |
||
649 | magazine_chain_next (prev) = next; |
||
650 | magazine_chain_prev (next) = prev; |
||
651 | /* clear special fields, put on trash stack */ |
||
652 | magazine_chain_next (current) = NULL; |
||
653 | magazine_chain_count (current) = NULL; |
||
654 | magazine_chain_stamp (current) = NULL; |
||
655 | magazine_chain_prev (current) = trash; |
||
656 | trash = current; |
||
657 | /* fixup list head if required */ |
||
658 | if (current == allocator->magazines[ix]) |
||
659 | { |
||
660 | allocator->magazines[ix] = NULL; |
||
661 | break; |
||
662 | } |
||
663 | current = prev; |
||
664 | } |
||
665 | g_mutex_unlock (&allocator->magazine_mutex); |
||
666 | /* free trash */ |
||
667 | if (trash) |
||
668 | { |
||
669 | const gsize chunk_size = SLAB_CHUNK_SIZE (allocator, ix); |
||
670 | g_mutex_lock (&allocator->slab_mutex); |
||
671 | while (trash) |
||
672 | { |
||
673 | current = trash; |
||
674 | trash = magazine_chain_prev (current); |
||
675 | magazine_chain_prev (current) = NULL; /* clear special field */ |
||
676 | while (current) |
||
677 | { |
||
678 | ChunkLink *chunk = magazine_chain_pop_head (¤t); |
||
679 | slab_allocator_free_chunk (chunk_size, chunk); |
||
680 | } |
||
681 | } |
||
682 | g_mutex_unlock (&allocator->slab_mutex); |
||
683 | } |
||
684 | } |
||
685 | |||
686 | static void |
||
687 | magazine_cache_push_magazine (guint ix, |
||
688 | ChunkLink *magazine_chunks, |
||
689 | gsize count) /* must be >= MIN_MAGAZINE_SIZE */ |
||
690 | { |
||
691 | ChunkLink *current = magazine_chain_prepare_fields (magazine_chunks); |
||
692 | ChunkLink *next, *prev; |
||
693 | g_mutex_lock (&allocator->magazine_mutex); |
||
694 | /* add magazine at head */ |
||
695 | next = allocator->magazines[ix]; |
||
696 | if (next) |
||
697 | prev = magazine_chain_prev (next); |
||
698 | else |
||
699 | next = prev = current; |
||
700 | magazine_chain_next (prev) = current; |
||
701 | magazine_chain_prev (next) = current; |
||
702 | magazine_chain_prev (current) = prev; |
||
703 | magazine_chain_next (current) = next; |
||
704 | magazine_chain_count (current) = (gpointer) count; |
||
705 | /* stamp magazine */ |
||
706 | magazine_cache_update_stamp(); |
||
707 | magazine_chain_stamp (current) = GUINT_TO_POINTER (allocator->last_stamp); |
||
708 | allocator->magazines[ix] = current; |
||
709 | /* free old magazines beyond a certain threshold */ |
||
710 | magazine_cache_trim (allocator, ix, allocator->last_stamp); |
||
711 | /* g_mutex_unlock (allocator->mutex); was done by magazine_cache_trim() */ |
||
712 | } |
||
713 | |||
714 | static ChunkLink* |
||
715 | magazine_cache_pop_magazine (guint ix, |
||
716 | gsize *countp) |
||
717 | { |
||
718 | g_mutex_lock_a (&allocator->magazine_mutex, &allocator->contention_counters[ix]); |
||
719 | if (!allocator->magazines[ix]) |
||
720 | { |
||
721 | guint magazine_threshold = allocator_get_magazine_threshold (allocator, ix); |
||
722 | gsize i, chunk_size = SLAB_CHUNK_SIZE (allocator, ix); |
||
723 | ChunkLink *chunk, *head; |
||
724 | g_mutex_unlock (&allocator->magazine_mutex); |
||
725 | g_mutex_lock (&allocator->slab_mutex); |
||
726 | head = slab_allocator_alloc_chunk (chunk_size); |
||
727 | head->data = NULL; |
||
728 | chunk = head; |
||
729 | for (i = 1; i < magazine_threshold; i++) |
||
730 | { |
||
731 | chunk->next = slab_allocator_alloc_chunk (chunk_size); |
||
732 | chunk = chunk->next; |
||
733 | chunk->data = NULL; |
||
734 | } |
||
735 | chunk->next = NULL; |
||
736 | g_mutex_unlock (&allocator->slab_mutex); |
||
737 | *countp = i; |
||
738 | return head; |
||
739 | } |
||
740 | else |
||
741 | { |
||
742 | ChunkLink *current = allocator->magazines[ix]; |
||
743 | ChunkLink *prev = magazine_chain_prev (current); |
||
744 | ChunkLink *next = magazine_chain_next (current); |
||
745 | /* unlink */ |
||
746 | magazine_chain_next (prev) = next; |
||
747 | magazine_chain_prev (next) = prev; |
||
748 | allocator->magazines[ix] = next == current ? NULL : next; |
||
749 | g_mutex_unlock (&allocator->magazine_mutex); |
||
750 | /* clear special fields and hand out */ |
||
751 | *countp = (gsize) magazine_chain_count (current); |
||
752 | magazine_chain_prev (current) = NULL; |
||
753 | magazine_chain_next (current) = NULL; |
||
754 | magazine_chain_count (current) = NULL; |
||
755 | magazine_chain_stamp (current) = NULL; |
||
756 | return current; |
||
757 | } |
||
758 | } |
||
759 | |||
760 | /* --- thread magazines --- */ |
||
761 | static void |
||
762 | private_thread_memory_cleanup (gpointer data) |
||
763 | { |
||
764 | ThreadMemory *tmem = data; |
||
765 | const guint n_magazines = MAX_SLAB_INDEX (allocator); |
||
766 | guint ix; |
||
767 | for (ix = 0; ix < n_magazines; ix++) |
||
768 | { |
||
769 | Magazine *mags[2]; |
||
770 | guint j; |
||
771 | mags[0] = &tmem->magazine1[ix]; |
||
772 | mags[1] = &tmem->magazine2[ix]; |
||
773 | for (j = 0; j < 2; j++) |
||
774 | { |
||
775 | Magazine *mag = mags[j]; |
||
776 | if (mag->count >= MIN_MAGAZINE_SIZE) |
||
777 | magazine_cache_push_magazine (ix, mag->chunks, mag->count); |
||
778 | else |
||
779 | { |
||
780 | const gsize chunk_size = SLAB_CHUNK_SIZE (allocator, ix); |
||
781 | g_mutex_lock (&allocator->slab_mutex); |
||
782 | while (mag->chunks) |
||
783 | { |
||
784 | ChunkLink *chunk = magazine_chain_pop_head (&mag->chunks); |
||
785 | slab_allocator_free_chunk (chunk_size, chunk); |
||
786 | } |
||
787 | g_mutex_unlock (&allocator->slab_mutex); |
||
788 | } |
||
789 | } |
||
790 | } |
||
791 | g_free (tmem); |
||
792 | } |
||
793 | |||
794 | static void |
||
795 | thread_memory_magazine1_reload (ThreadMemory *tmem, |
||
796 | guint ix) |
||
797 | { |
||
798 | Magazine *mag = &tmem->magazine1[ix]; |
||
799 | mem_assert (mag->chunks == NULL); /* ensure that we may reset mag->count */ |
||
800 | mag->count = 0; |
||
801 | mag->chunks = magazine_cache_pop_magazine (ix, &mag->count); |
||
802 | } |
||
803 | |||
804 | static void |
||
805 | thread_memory_magazine2_unload (ThreadMemory *tmem, |
||
806 | guint ix) |
||
807 | { |
||
808 | Magazine *mag = &tmem->magazine2[ix]; |
||
809 | magazine_cache_push_magazine (ix, mag->chunks, mag->count); |
||
810 | mag->chunks = NULL; |
||
811 | mag->count = 0; |
||
812 | } |
||
813 | |||
814 | static inline void |
||
815 | thread_memory_swap_magazines (ThreadMemory *tmem, |
||
816 | guint ix) |
||
817 | { |
||
818 | Magazine xmag = tmem->magazine1[ix]; |
||
819 | tmem->magazine1[ix] = tmem->magazine2[ix]; |
||
820 | tmem->magazine2[ix] = xmag; |
||
821 | } |
||
822 | |||
823 | static inline gboolean |
||
824 | thread_memory_magazine1_is_empty (ThreadMemory *tmem, |
||
825 | guint ix) |
||
826 | { |
||
827 | return tmem->magazine1[ix].chunks == NULL; |
||
828 | } |
||
829 | |||
830 | static inline gboolean |
||
831 | thread_memory_magazine2_is_full (ThreadMemory *tmem, |
||
832 | guint ix) |
||
833 | { |
||
834 | return tmem->magazine2[ix].count >= allocator_get_magazine_threshold (allocator, ix); |
||
835 | } |
||
836 | |||
837 | static inline gpointer |
||
838 | thread_memory_magazine1_alloc (ThreadMemory *tmem, |
||
839 | guint ix) |
||
840 | { |
||
841 | Magazine *mag = &tmem->magazine1[ix]; |
||
842 | ChunkLink *chunk = magazine_chain_pop_head (&mag->chunks); |
||
843 | if (G_LIKELY (mag->count > 0)) |
||
844 | mag->count--; |
||
845 | return chunk; |
||
846 | } |
||
847 | |||
848 | static inline void |
||
849 | thread_memory_magazine2_free (ThreadMemory *tmem, |
||
850 | guint ix, |
||
851 | gpointer mem) |
||
852 | { |
||
853 | Magazine *mag = &tmem->magazine2[ix]; |
||
854 | ChunkLink *chunk = mem; |
||
855 | chunk->data = NULL; |
||
856 | chunk->next = mag->chunks; |
||
857 | mag->chunks = chunk; |
||
858 | mag->count++; |
||
859 | } |
||
860 | |||
861 | /* --- API functions --- */ |
||
862 | |||
863 | /** |
||
864 | * g_slice_new: |
||
865 | * @type: the type to allocate, typically a structure name |
||
866 | * |
||
867 | * A convenience macro to allocate a block of memory from the |
||
868 | * slice allocator. |
||
869 | * |
||
870 | * It calls g_slice_alloc() with `sizeof (@type)` and casts the |
||
871 | * returned pointer to a pointer of the given type, avoiding a type |
||
872 | * cast in the source code. Note that the underlying slice allocation |
||
873 | * mechanism can be changed with the [`G_SLICE=always-malloc`][G_SLICE] |
||
874 | * environment variable. |
||
875 | * |
||
876 | * This can never return %NULL as the minimum allocation size from |
||
877 | * `sizeof (@type)` is 1 byte. |
||
878 | * |
||
879 | * Returns: (not nullable): a pointer to the allocated block, cast to a pointer |
||
880 | * to @type |
||
881 | * |
||
882 | * Since: 2.10 |
||
883 | */ |
||
884 | |||
885 | /** |
||
886 | * g_slice_new0: |
||
887 | * @type: the type to allocate, typically a structure name |
||
888 | * |
||
889 | * A convenience macro to allocate a block of memory from the |
||
890 | * slice allocator and set the memory to 0. |
||
891 | * |
||
892 | * It calls g_slice_alloc0() with `sizeof (@type)` |
||
893 | * and casts the returned pointer to a pointer of the given type, |
||
894 | * avoiding a type cast in the source code. |
||
895 | * Note that the underlying slice allocation mechanism can |
||
896 | * be changed with the [`G_SLICE=always-malloc`][G_SLICE] |
||
897 | * environment variable. |
||
898 | * |
||
899 | * This can never return %NULL as the minimum allocation size from |
||
900 | * `sizeof (@type)` is 1 byte. |
||
901 | * |
||
902 | * Returns: (not nullable): a pointer to the allocated block, cast to a pointer |
||
903 | * to @type |
||
904 | * |
||
905 | * Since: 2.10 |
||
906 | */ |
||
907 | |||
908 | /** |
||
909 | * g_slice_dup: |
||
910 | * @type: the type to duplicate, typically a structure name |
||
911 | * @mem: (not nullable): the memory to copy into the allocated block |
||
912 | * |
||
913 | * A convenience macro to duplicate a block of memory using |
||
914 | * the slice allocator. |
||
915 | * |
||
916 | * It calls g_slice_copy() with `sizeof (@type)` |
||
917 | * and casts the returned pointer to a pointer of the given type, |
||
918 | * avoiding a type cast in the source code. |
||
919 | * Note that the underlying slice allocation mechanism can |
||
920 | * be changed with the [`G_SLICE=always-malloc`][G_SLICE] |
||
921 | * environment variable. |
||
922 | * |
||
923 | * This can never return %NULL. |
||
924 | * |
||
925 | * Returns: (not nullable): a pointer to the allocated block, cast to a pointer |
||
926 | * to @type |
||
927 | * |
||
928 | * Since: 2.14 |
||
929 | */ |
||
930 | |||
931 | /** |
||
932 | * g_slice_free: |
||
933 | * @type: the type of the block to free, typically a structure name |
||
934 | * @mem: a pointer to the block to free |
||
935 | * |
||
936 | * A convenience macro to free a block of memory that has |
||
937 | * been allocated from the slice allocator. |
||
938 | * |
||
939 | * It calls g_slice_free1() using `sizeof (type)` |
||
940 | * as the block size. |
||
941 | * Note that the exact release behaviour can be changed with the |
||
942 | * [`G_DEBUG=gc-friendly`][G_DEBUG] environment variable, also see |
||
943 | * [`G_SLICE`][G_SLICE] for related debugging options. |
||
944 | * |
||
945 | * If @mem is %NULL, this macro does nothing. |
||
946 | * |
||
947 | * Since: 2.10 |
||
948 | */ |
||
949 | |||
950 | /** |
||
951 | * g_slice_free_chain: |
||
952 | * @type: the type of the @mem_chain blocks |
||
953 | * @mem_chain: a pointer to the first block of the chain |
||
954 | * @next: the field name of the next pointer in @type |
||
955 | * |
||
956 | * Frees a linked list of memory blocks of structure type @type. |
||
957 | * The memory blocks must be equal-sized, allocated via |
||
958 | * g_slice_alloc() or g_slice_alloc0() and linked together by |
||
959 | * a @next pointer (similar to #GSList). The name of the |
||
960 | * @next field in @type is passed as third argument. |
||
961 | * Note that the exact release behaviour can be changed with the |
||
962 | * [`G_DEBUG=gc-friendly`][G_DEBUG] environment variable, also see |
||
963 | * [`G_SLICE`][G_SLICE] for related debugging options. |
||
964 | * |
||
965 | * If @mem_chain is %NULL, this function does nothing. |
||
966 | * |
||
967 | * Since: 2.10 |
||
968 | */ |
||
969 | |||
970 | /** |
||
971 | * g_slice_alloc: |
||
972 | * @block_size: the number of bytes to allocate |
||
973 | * |
||
974 | * Allocates a block of memory from the slice allocator. |
||
975 | * The block adress handed out can be expected to be aligned |
||
976 | * to at least 1 * sizeof (void*), |
||
977 | * though in general slices are 2 * sizeof (void*) bytes aligned, |
||
978 | * if a malloc() fallback implementation is used instead, |
||
979 | * the alignment may be reduced in a libc dependent fashion. |
||
980 | * Note that the underlying slice allocation mechanism can |
||
981 | * be changed with the [`G_SLICE=always-malloc`][G_SLICE] |
||
982 | * environment variable. |
||
983 | * |
||
984 | * Returns: a pointer to the allocated memory block, which will be %NULL if and |
||
985 | * only if @mem_size is 0 |
||
986 | * |
||
987 | * Since: 2.10 |
||
988 | */ |
||
989 | gpointer |
||
990 | g_slice_alloc (gsize mem_size) |
||
991 | { |
||
992 | ThreadMemory *tmem; |
||
993 | gsize chunk_size; |
||
994 | gpointer mem; |
||
995 | guint acat; |
||
996 | |||
997 | /* This gets the private structure for this thread. If the private |
||
998 | * structure does not yet exist, it is created. |
||
999 | * |
||
1000 | * This has a side effect of causing GSlice to be initialised, so it |
||
1001 | * must come first. |
||
1002 | */ |
||
1003 | tmem = thread_memory_from_self (); |
||
1004 | |||
1005 | chunk_size = P2ALIGN (mem_size); |
||
1006 | acat = allocator_categorize (chunk_size); |
||
1007 | if (G_LIKELY (acat == 1)) /* allocate through magazine layer */ |
||
1008 | { |
||
1009 | guint ix = SLAB_INDEX (allocator, chunk_size); |
||
1010 | if (G_UNLIKELY (thread_memory_magazine1_is_empty (tmem, ix))) |
||
1011 | { |
||
1012 | thread_memory_swap_magazines (tmem, ix); |
||
1013 | if (G_UNLIKELY (thread_memory_magazine1_is_empty (tmem, ix))) |
||
1014 | thread_memory_magazine1_reload (tmem, ix); |
||
1015 | } |
||
1016 | mem = thread_memory_magazine1_alloc (tmem, ix); |
||
1017 | } |
||
1018 | else if (acat == 2) /* allocate through slab allocator */ |
||
1019 | { |
||
1020 | g_mutex_lock (&allocator->slab_mutex); |
||
1021 | mem = slab_allocator_alloc_chunk (chunk_size); |
||
1022 | g_mutex_unlock (&allocator->slab_mutex); |
||
1023 | } |
||
1024 | else /* delegate to system malloc */ |
||
1025 | mem = g_malloc (mem_size); |
||
1026 | if (G_UNLIKELY (allocator->config.debug_blocks)) |
||
1027 | smc_notify_alloc (mem, mem_size); |
||
1028 | |||
1029 | TRACE (GLIB_SLICE_ALLOC((void*)mem, mem_size)); |
||
1030 | |||
1031 | return mem; |
||
1032 | } |
||
1033 | |||
1034 | /** |
||
1035 | * g_slice_alloc0: |
||
1036 | * @block_size: the number of bytes to allocate |
||
1037 | * |
||
1038 | * Allocates a block of memory via g_slice_alloc() and initializes |
||
1039 | * the returned memory to 0. Note that the underlying slice allocation |
||
1040 | * mechanism can be changed with the [`G_SLICE=always-malloc`][G_SLICE] |
||
1041 | * environment variable. |
||
1042 | * |
||
1043 | * Returns: a pointer to the allocated block, which will be %NULL if and only |
||
1044 | * if @mem_size is 0 |
||
1045 | * |
||
1046 | * Since: 2.10 |
||
1047 | */ |
||
1048 | gpointer |
||
1049 | g_slice_alloc0 (gsize mem_size) |
||
1050 | { |
||
1051 | gpointer mem = g_slice_alloc (mem_size); |
||
1052 | if (mem) |
||
1053 | memset (mem, 0, mem_size); |
||
1054 | return mem; |
||
1055 | } |
||
1056 | |||
1057 | /** |
||
1058 | * g_slice_copy: |
||
1059 | * @block_size: the number of bytes to allocate |
||
1060 | * @mem_block: the memory to copy |
||
1061 | * |
||
1062 | * Allocates a block of memory from the slice allocator |
||
1063 | * and copies @block_size bytes into it from @mem_block. |
||
1064 | * |
||
1065 | * @mem_block must be non-%NULL if @block_size is non-zero. |
||
1066 | * |
||
1067 | * Returns: a pointer to the allocated memory block, which will be %NULL if and |
||
1068 | * only if @mem_size is 0 |
||
1069 | * |
||
1070 | * Since: 2.14 |
||
1071 | */ |
||
1072 | gpointer |
||
1073 | g_slice_copy (gsize mem_size, |
||
1074 | gconstpointer mem_block) |
||
1075 | { |
||
1076 | gpointer mem = g_slice_alloc (mem_size); |
||
1077 | if (mem) |
||
1078 | memcpy (mem, mem_block, mem_size); |
||
1079 | return mem; |
||
1080 | } |
||
1081 | |||
1082 | /** |
||
1083 | * g_slice_free1: |
||
1084 | * @block_size: the size of the block |
||
1085 | * @mem_block: a pointer to the block to free |
||
1086 | * |
||
1087 | * Frees a block of memory. |
||
1088 | * |
||
1089 | * The memory must have been allocated via g_slice_alloc() or |
||
1090 | * g_slice_alloc0() and the @block_size has to match the size |
||
1091 | * specified upon allocation. Note that the exact release behaviour |
||
1092 | * can be changed with the [`G_DEBUG=gc-friendly`][G_DEBUG] environment |
||
1093 | * variable, also see [`G_SLICE`][G_SLICE] for related debugging options. |
||
1094 | * |
||
1095 | * If @mem_block is %NULL, this function does nothing. |
||
1096 | * |
||
1097 | * Since: 2.10 |
||
1098 | */ |
||
1099 | void |
||
1100 | g_slice_free1 (gsize mem_size, |
||
1101 | gpointer mem_block) |
||
1102 | { |
||
1103 | gsize chunk_size = P2ALIGN (mem_size); |
||
1104 | guint acat = allocator_categorize (chunk_size); |
||
1105 | if (G_UNLIKELY (!mem_block)) |
||
1106 | return; |
||
1107 | if (G_UNLIKELY (allocator->config.debug_blocks) && |
||
1108 | !smc_notify_free (mem_block, mem_size)) |
||
1109 | abort(); |
||
1110 | if (G_LIKELY (acat == 1)) /* allocate through magazine layer */ |
||
1111 | { |
||
1112 | ThreadMemory *tmem = thread_memory_from_self(); |
||
1113 | guint ix = SLAB_INDEX (allocator, chunk_size); |
||
1114 | if (G_UNLIKELY (thread_memory_magazine2_is_full (tmem, ix))) |
||
1115 | { |
||
1116 | thread_memory_swap_magazines (tmem, ix); |
||
1117 | if (G_UNLIKELY (thread_memory_magazine2_is_full (tmem, ix))) |
||
1118 | thread_memory_magazine2_unload (tmem, ix); |
||
1119 | } |
||
1120 | if (G_UNLIKELY (g_mem_gc_friendly)) |
||
1121 | memset (mem_block, 0, chunk_size); |
||
1122 | thread_memory_magazine2_free (tmem, ix, mem_block); |
||
1123 | } |
||
1124 | else if (acat == 2) /* allocate through slab allocator */ |
||
1125 | { |
||
1126 | if (G_UNLIKELY (g_mem_gc_friendly)) |
||
1127 | memset (mem_block, 0, chunk_size); |
||
1128 | g_mutex_lock (&allocator->slab_mutex); |
||
1129 | slab_allocator_free_chunk (chunk_size, mem_block); |
||
1130 | g_mutex_unlock (&allocator->slab_mutex); |
||
1131 | } |
||
1132 | else /* delegate to system malloc */ |
||
1133 | { |
||
1134 | if (G_UNLIKELY (g_mem_gc_friendly)) |
||
1135 | memset (mem_block, 0, mem_size); |
||
1136 | g_free (mem_block); |
||
1137 | } |
||
1138 | TRACE (GLIB_SLICE_FREE((void*)mem_block, mem_size)); |
||
1139 | } |
||
1140 | |||
1141 | /** |
||
1142 | * g_slice_free_chain_with_offset: |
||
1143 | * @block_size: the size of the blocks |
||
1144 | * @mem_chain: a pointer to the first block of the chain |
||
1145 | * @next_offset: the offset of the @next field in the blocks |
||
1146 | * |
||
1147 | * Frees a linked list of memory blocks of structure type @type. |
||
1148 | * |
||
1149 | * The memory blocks must be equal-sized, allocated via |
||
1150 | * g_slice_alloc() or g_slice_alloc0() and linked together by a |
||
1151 | * @next pointer (similar to #GSList). The offset of the @next |
||
1152 | * field in each block is passed as third argument. |
||
1153 | * Note that the exact release behaviour can be changed with the |
||
1154 | * [`G_DEBUG=gc-friendly`][G_DEBUG] environment variable, also see |
||
1155 | * [`G_SLICE`][G_SLICE] for related debugging options. |
||
1156 | * |
||
1157 | * If @mem_chain is %NULL, this function does nothing. |
||
1158 | * |
||
1159 | * Since: 2.10 |
||
1160 | */ |
||
1161 | void |
||
1162 | g_slice_free_chain_with_offset (gsize mem_size, |
||
1163 | gpointer mem_chain, |
||
1164 | gsize next_offset) |
||
1165 | { |
||
1166 | gpointer slice = mem_chain; |
||
1167 | /* while the thread magazines and the magazine cache are implemented so that |
||
1168 | * they can easily be extended to allow for free lists containing more free |
||
1169 | * lists for the first level nodes, which would allow O(1) freeing in this |
||
1170 | * function, the benefit of such an extension is questionable, because: |
||
1171 | * - the magazine size counts will become mere lower bounds which confuses |
||
1172 | * the code adapting to lock contention; |
||
1173 | * - freeing a single node to the thread magazines is very fast, so this |
||
1174 | * O(list_length) operation is multiplied by a fairly small factor; |
||
1175 | * - memory usage histograms on larger applications seem to indicate that |
||
1176 | * the amount of released multi node lists is negligible in comparison |
||
1177 | * to single node releases. |
||
1178 | * - the major performance bottle neck, namely g_private_get() or |
||
1179 | * g_mutex_lock()/g_mutex_unlock() has already been moved out of the |
||
1180 | * inner loop for freeing chained slices. |
||
1181 | */ |
||
1182 | gsize chunk_size = P2ALIGN (mem_size); |
||
1183 | guint acat = allocator_categorize (chunk_size); |
||
1184 | if (G_LIKELY (acat == 1)) /* allocate through magazine layer */ |
||
1185 | { |
||
1186 | ThreadMemory *tmem = thread_memory_from_self(); |
||
1187 | guint ix = SLAB_INDEX (allocator, chunk_size); |
||
1188 | while (slice) |
||
1189 | { |
||
1190 | guint8 *current = slice; |
||
1191 | slice = *(gpointer*) (current + next_offset); |
||
1192 | if (G_UNLIKELY (allocator->config.debug_blocks) && |
||
1193 | !smc_notify_free (current, mem_size)) |
||
1194 | abort(); |
||
1195 | if (G_UNLIKELY (thread_memory_magazine2_is_full (tmem, ix))) |
||
1196 | { |
||
1197 | thread_memory_swap_magazines (tmem, ix); |
||
1198 | if (G_UNLIKELY (thread_memory_magazine2_is_full (tmem, ix))) |
||
1199 | thread_memory_magazine2_unload (tmem, ix); |
||
1200 | } |
||
1201 | if (G_UNLIKELY (g_mem_gc_friendly)) |
||
1202 | memset (current, 0, chunk_size); |
||
1203 | thread_memory_magazine2_free (tmem, ix, current); |
||
1204 | } |
||
1205 | } |
||
1206 | else if (acat == 2) /* allocate through slab allocator */ |
||
1207 | { |
||
1208 | g_mutex_lock (&allocator->slab_mutex); |
||
1209 | while (slice) |
||
1210 | { |
||
1211 | guint8 *current = slice; |
||
1212 | slice = *(gpointer*) (current + next_offset); |
||
1213 | if (G_UNLIKELY (allocator->config.debug_blocks) && |
||
1214 | !smc_notify_free (current, mem_size)) |
||
1215 | abort(); |
||
1216 | if (G_UNLIKELY (g_mem_gc_friendly)) |
||
1217 | memset (current, 0, chunk_size); |
||
1218 | slab_allocator_free_chunk (chunk_size, current); |
||
1219 | } |
||
1220 | g_mutex_unlock (&allocator->slab_mutex); |
||
1221 | } |
||
1222 | else /* delegate to system malloc */ |
||
1223 | while (slice) |
||
1224 | { |
||
1225 | guint8 *current = slice; |
||
1226 | slice = *(gpointer*) (current + next_offset); |
||
1227 | if (G_UNLIKELY (allocator->config.debug_blocks) && |
||
1228 | !smc_notify_free (current, mem_size)) |
||
1229 | abort(); |
||
1230 | if (G_UNLIKELY (g_mem_gc_friendly)) |
||
1231 | memset (current, 0, mem_size); |
||
1232 | g_free (current); |
||
1233 | } |
||
1234 | } |
||
1235 | |||
1236 | /* --- single page allocator --- */ |
||
1237 | static void |
||
1238 | allocator_slab_stack_push (Allocator *allocator, |
||
1239 | guint ix, |
||
1240 | SlabInfo *sinfo) |
||
1241 | { |
||
1242 | /* insert slab at slab ring head */ |
||
1243 | if (!allocator->slab_stack[ix]) |
||
1244 | { |
||
1245 | sinfo->next = sinfo; |
||
1246 | sinfo->prev = sinfo; |
||
1247 | } |
||
1248 | else |
||
1249 | { |
||
1250 | SlabInfo *next = allocator->slab_stack[ix], *prev = next->prev; |
||
1251 | next->prev = sinfo; |
||
1252 | prev->next = sinfo; |
||
1253 | sinfo->next = next; |
||
1254 | sinfo->prev = prev; |
||
1255 | } |
||
1256 | allocator->slab_stack[ix] = sinfo; |
||
1257 | } |
||
1258 | |||
1259 | static gsize |
||
1260 | allocator_aligned_page_size (Allocator *allocator, |
||
1261 | gsize n_bytes) |
||
1262 | { |
||
1263 | gsize val = 1 << g_bit_storage (n_bytes - 1); |
||
1264 | val = MAX (val, allocator->min_page_size); |
||
1265 | return val; |
||
1266 | } |
||
1267 | |||
1268 | static void |
||
1269 | allocator_add_slab (Allocator *allocator, |
||
1270 | guint ix, |
||
1271 | gsize chunk_size) |
||
1272 | { |
||
1273 | ChunkLink *chunk; |
||
1274 | SlabInfo *sinfo; |
||
1275 | gsize addr, padding, n_chunks, color = 0; |
||
1276 | gsize page_size = allocator_aligned_page_size (allocator, SLAB_BPAGE_SIZE (allocator, chunk_size)); |
||
1277 | /* allocate 1 page for the chunks and the slab */ |
||
1278 | gpointer aligned_memory = allocator_memalign (page_size, page_size - NATIVE_MALLOC_PADDING); |
||
1279 | guint8 *mem = aligned_memory; |
||
1280 | guint i; |
||
1281 | if (!mem) |
||
1282 | { |
||
1283 | const gchar *syserr = strerror (errno); |
||
1284 | mem_error ("failed to allocate %u bytes (alignment: %u): %s\n", |
||
1285 | (guint) (page_size - NATIVE_MALLOC_PADDING), (guint) page_size, syserr); |
||
1286 | } |
||
1287 | /* mask page address */ |
||
1288 | addr = ((gsize) mem / page_size) * page_size; |
||
1289 | /* assert alignment */ |
||
1290 | mem_assert (aligned_memory == (gpointer) addr); |
||
1291 | /* basic slab info setup */ |
||
1292 | sinfo = (SlabInfo*) (mem + page_size - SLAB_INFO_SIZE); |
||
1293 | sinfo->n_allocated = 0; |
||
1294 | sinfo->chunks = NULL; |
||
1295 | /* figure cache colorization */ |
||
1296 | n_chunks = ((guint8*) sinfo - mem) / chunk_size; |
||
1297 | padding = ((guint8*) sinfo - mem) - n_chunks * chunk_size; |
||
1298 | if (padding) |
||
1299 | { |
||
1300 | color = (allocator->color_accu * P2ALIGNMENT) % padding; |
||
1301 | allocator->color_accu += allocator->config.color_increment; |
||
1302 | } |
||
1303 | /* add chunks to free list */ |
||
1304 | chunk = (ChunkLink*) (mem + color); |
||
1305 | sinfo->chunks = chunk; |
||
1306 | for (i = 0; i < n_chunks - 1; i++) |
||
1307 | { |
||
1308 | chunk->next = (ChunkLink*) ((guint8*) chunk + chunk_size); |
||
1309 | chunk = chunk->next; |
||
1310 | } |
||
1311 | chunk->next = NULL; /* last chunk */ |
||
1312 | /* add slab to slab ring */ |
||
1313 | allocator_slab_stack_push (allocator, ix, sinfo); |
||
1314 | } |
||
1315 | |||
1316 | static gpointer |
||
1317 | slab_allocator_alloc_chunk (gsize chunk_size) |
||
1318 | { |
||
1319 | ChunkLink *chunk; |
||
1320 | guint ix = SLAB_INDEX (allocator, chunk_size); |
||
1321 | /* ensure non-empty slab */ |
||
1322 | if (!allocator->slab_stack[ix] || !allocator->slab_stack[ix]->chunks) |
||
1323 | allocator_add_slab (allocator, ix, chunk_size); |
||
1324 | /* allocate chunk */ |
||
1325 | chunk = allocator->slab_stack[ix]->chunks; |
||
1326 | allocator->slab_stack[ix]->chunks = chunk->next; |
||
1327 | allocator->slab_stack[ix]->n_allocated++; |
||
1328 | /* rotate empty slabs */ |
||
1329 | if (!allocator->slab_stack[ix]->chunks) |
||
1330 | allocator->slab_stack[ix] = allocator->slab_stack[ix]->next; |
||
1331 | return chunk; |
||
1332 | } |
||
1333 | |||
1334 | static void |
||
1335 | slab_allocator_free_chunk (gsize chunk_size, |
||
1336 | gpointer mem) |
||
1337 | { |
||
1338 | ChunkLink *chunk; |
||
1339 | gboolean was_empty; |
||
1340 | guint ix = SLAB_INDEX (allocator, chunk_size); |
||
1341 | gsize page_size = allocator_aligned_page_size (allocator, SLAB_BPAGE_SIZE (allocator, chunk_size)); |
||
1342 | gsize addr = ((gsize) mem / page_size) * page_size; |
||
1343 | /* mask page address */ |
||
1344 | guint8 *page = (guint8*) addr; |
||
1345 | SlabInfo *sinfo = (SlabInfo*) (page + page_size - SLAB_INFO_SIZE); |
||
1346 | /* assert valid chunk count */ |
||
1347 | mem_assert (sinfo->n_allocated > 0); |
||
1348 | /* add chunk to free list */ |
||
1349 | was_empty = sinfo->chunks == NULL; |
||
1350 | chunk = (ChunkLink*) mem; |
||
1351 | chunk->next = sinfo->chunks; |
||
1352 | sinfo->chunks = chunk; |
||
1353 | sinfo->n_allocated--; |
||
1354 | /* keep slab ring partially sorted, empty slabs at end */ |
||
1355 | if (was_empty) |
||
1356 | { |
||
1357 | /* unlink slab */ |
||
1358 | SlabInfo *next = sinfo->next, *prev = sinfo->prev; |
||
1359 | next->prev = prev; |
||
1360 | prev->next = next; |
||
1361 | if (allocator->slab_stack[ix] == sinfo) |
||
1362 | allocator->slab_stack[ix] = next == sinfo ? NULL : next; |
||
1363 | /* insert slab at head */ |
||
1364 | allocator_slab_stack_push (allocator, ix, sinfo); |
||
1365 | } |
||
1366 | /* eagerly free complete unused slabs */ |
||
1367 | if (!sinfo->n_allocated) |
||
1368 | { |
||
1369 | /* unlink slab */ |
||
1370 | SlabInfo *next = sinfo->next, *prev = sinfo->prev; |
||
1371 | next->prev = prev; |
||
1372 | prev->next = next; |
||
1373 | if (allocator->slab_stack[ix] == sinfo) |
||
1374 | allocator->slab_stack[ix] = next == sinfo ? NULL : next; |
||
1375 | /* free slab */ |
||
1376 | allocator_memfree (page_size, page); |
||
1377 | } |
||
1378 | } |
||
1379 | |||
1380 | /* --- memalign implementation --- */ |
||
1381 | #ifdef HAVE_MALLOC_H |
||
1382 | #include <malloc.h> /* memalign() */ |
||
1383 | #endif |
||
1384 | |||
1385 | /* from config.h: |
||
1386 | * define HAVE_POSIX_MEMALIGN 1 // if free(posix_memalign(3)) works, <stdlib.h> |
||
1387 | * define HAVE_COMPLIANT_POSIX_MEMALIGN 1 // if free(posix_memalign(3)) works for sizes != 2^n, <stdlib.h> |
||
1388 | * define HAVE_MEMALIGN 1 // if free(memalign(3)) works, <malloc.h> |
||
1389 | * define HAVE_VALLOC 1 // if free(valloc(3)) works, <stdlib.h> or <malloc.h> |
||
1390 | * if none is provided, we implement malloc(3)-based alloc-only page alignment |
||
1391 | */ |
||
1392 | |||
1393 | #if !(HAVE_COMPLIANT_POSIX_MEMALIGN || HAVE_MEMALIGN || HAVE_VALLOC) |
||
1394 | static GTrashStack *compat_valloc_trash = NULL; |
||
1395 | #endif |
||
1396 | |||
1397 | static gpointer |
||
1398 | allocator_memalign (gsize alignment, |
||
1399 | gsize memsize) |
||
1400 | { |
||
1401 | gpointer aligned_memory = NULL; |
||
1402 | gint err = ENOMEM; |
||
1403 | #if HAVE_COMPLIANT_POSIX_MEMALIGN |
||
1404 | err = posix_memalign (&aligned_memory, alignment, memsize); |
||
1405 | #elif HAVE_MEMALIGN |
||
1406 | errno = 0; |
||
1407 | aligned_memory = memalign (alignment, memsize); |
||
1408 | err = errno; |
||
1409 | #elif HAVE_VALLOC |
||
1410 | errno = 0; |
||
1411 | aligned_memory = valloc (memsize); |
||
1412 | err = errno; |
||
1413 | #else |
||
1414 | /* simplistic non-freeing page allocator */ |
||
1415 | mem_assert (alignment == sys_page_size); |
||
1416 | mem_assert (memsize <= sys_page_size); |
||
1417 | if (!compat_valloc_trash) |
||
1418 | { |
||
1419 | const guint n_pages = 16; |
||
1420 | guint8 *mem = malloc (n_pages * sys_page_size); |
||
1421 | err = errno; |
||
1422 | if (mem) |
||
1423 | { |
||
1424 | gint i = n_pages; |
||
1425 | guint8 *amem = (guint8*) ALIGN ((gsize) mem, sys_page_size); |
||
1426 | if (amem != mem) |
||
1427 | i--; /* mem wasn't page aligned */ |
||
1428 | while (--i >= 0) |
||
1429 | g_trash_stack_push (&compat_valloc_trash, amem + i * sys_page_size); |
||
1430 | } |
||
1431 | } |
||
1432 | aligned_memory = g_trash_stack_pop (&compat_valloc_trash); |
||
1433 | #endif |
||
1434 | if (!aligned_memory) |
||
1435 | errno = err; |
||
1436 | return aligned_memory; |
||
1437 | } |
||
1438 | |||
1439 | static void |
||
1440 | allocator_memfree (gsize memsize, |
||
1441 | gpointer mem) |
||
1442 | { |
||
1443 | #if HAVE_COMPLIANT_POSIX_MEMALIGN || HAVE_MEMALIGN || HAVE_VALLOC |
||
1444 | free (mem); |
||
1445 | #else |
||
1446 | mem_assert (memsize <= sys_page_size); |
||
1447 | g_trash_stack_push (&compat_valloc_trash, mem); |
||
1448 | #endif |
||
1449 | } |
||
1450 | |||
1451 | static void |
||
1452 | mem_error (const char *format, |
||
1453 | ...) |
||
1454 | { |
||
1455 | const char *pname; |
||
1456 | va_list args; |
||
1457 | /* at least, put out "MEMORY-ERROR", in case we segfault during the rest of the function */ |
||
1458 | fputs ("\n***MEMORY-ERROR***: ", stderr); |
||
1459 | pname = g_get_prgname(); |
||
1460 | fprintf (stderr, "%s[%ld]: GSlice: ", pname ? pname : "", (long)getpid()); |
||
1461 | va_start (args, format); |
||
1462 | vfprintf (stderr, format, args); |
||
1463 | va_end (args); |
||
1464 | fputs ("\n", stderr); |
||
1465 | abort(); |
||
1466 | _exit (1); |
||
1467 | } |
||
1468 | |||
1469 | /* --- g-slice memory checker tree --- */ |
||
1470 | typedef size_t SmcKType; /* key type */ |
||
1471 | typedef size_t SmcVType; /* value type */ |
||
1472 | typedef struct { |
||
1473 | SmcKType key; |
||
1474 | SmcVType value; |
||
1475 | } SmcEntry; |
||
1476 | static void smc_tree_insert (SmcKType key, |
||
1477 | SmcVType value); |
||
1478 | static gboolean smc_tree_lookup (SmcKType key, |
||
1479 | SmcVType *value_p); |
||
1480 | static gboolean smc_tree_remove (SmcKType key); |
||
1481 | |||
1482 | |||
1483 | /* --- g-slice memory checker implementation --- */ |
||
1484 | static void |
||
1485 | smc_notify_alloc (void *pointer, |
||
1486 | size_t size) |
||
1487 | { |
||
1488 | size_t adress = (size_t) pointer; |
||
1489 | if (pointer) |
||
1490 | smc_tree_insert (adress, size); |
||
1491 | } |
||
1492 | |||
1493 | #if 0 |
||
1494 | static void |
||
1495 | smc_notify_ignore (void *pointer) |
||
1496 | { |
||
1497 | size_t adress = (size_t) pointer; |
||
1498 | if (pointer) |
||
1499 | smc_tree_remove (adress); |
||
1500 | } |
||
1501 | #endif |
||
1502 | |||
1503 | static int |
||
1504 | smc_notify_free (void *pointer, |
||
1505 | size_t size) |
||
1506 | { |
||
1507 | size_t adress = (size_t) pointer; |
||
1508 | SmcVType real_size; |
||
1509 | gboolean found_one; |
||
1510 | |||
1511 | if (!pointer) |
||
1512 | return 1; /* ignore */ |
||
1513 | found_one = smc_tree_lookup (adress, &real_size); |
||
1514 | if (!found_one) |
||
1515 | { |
||
1516 | fprintf (stderr, "GSlice: MemChecker: attempt to release non-allocated block: %p size=%" G_GSIZE_FORMAT "\n", pointer, size); |
||
1517 | return 0; |
||
1518 | } |
||
1519 | if (real_size != size && (real_size || size)) |
||
1520 | { |
||
1521 | fprintf (stderr, "GSlice: MemChecker: attempt to release block with invalid size: %p size=%" G_GSIZE_FORMAT " invalid-size=%" G_GSIZE_FORMAT "\n", pointer, real_size, size); |
||
1522 | return 0; |
||
1523 | } |
||
1524 | if (!smc_tree_remove (adress)) |
||
1525 | { |
||
1526 | fprintf (stderr, "GSlice: MemChecker: attempt to release non-allocated block: %p size=%" G_GSIZE_FORMAT "\n", pointer, size); |
||
1527 | return 0; |
||
1528 | } |
||
1529 | return 1; /* all fine */ |
||
1530 | } |
||
1531 | |||
1532 | /* --- g-slice memory checker tree implementation --- */ |
||
1533 | #define SMC_TRUNK_COUNT (4093 /* 16381 */) /* prime, to distribute trunk collisions (big, allocated just once) */ |
||
1534 | #define SMC_BRANCH_COUNT (511) /* prime, to distribute branch collisions */ |
||
1535 | #define SMC_TRUNK_EXTENT (SMC_BRANCH_COUNT * 2039) /* key address space per trunk, should distribute uniformly across BRANCH_COUNT */ |
||
1536 | #define SMC_TRUNK_HASH(k) ((k / SMC_TRUNK_EXTENT) % SMC_TRUNK_COUNT) /* generate new trunk hash per megabyte (roughly) */ |
||
1537 | #define SMC_BRANCH_HASH(k) (k % SMC_BRANCH_COUNT) |
||
1538 | |||
1539 | typedef struct { |
||
1540 | SmcEntry *entries; |
||
1541 | unsigned int n_entries; |
||
1542 | } SmcBranch; |
||
1543 | |||
1544 | static SmcBranch **smc_tree_root = NULL; |
||
1545 | |||
1546 | static void |
||
1547 | smc_tree_abort (int errval) |
||
1548 | { |
||
1549 | const char *syserr = strerror (errval); |
||
1550 | mem_error ("MemChecker: failure in debugging tree: %s", syserr); |
||
1551 | } |
||
1552 | |||
1553 | static inline SmcEntry* |
||
1554 | smc_tree_branch_grow_L (SmcBranch *branch, |
||
1555 | unsigned int index) |
||
1556 | { |
||
1557 | unsigned int old_size = branch->n_entries * sizeof (branch->entries[0]); |
||
1558 | unsigned int new_size = old_size + sizeof (branch->entries[0]); |
||
1559 | SmcEntry *entry; |
||
1560 | mem_assert (index <= branch->n_entries); |
||
1561 | branch->entries = (SmcEntry*) realloc (branch->entries, new_size); |
||
1562 | if (!branch->entries) |
||
1563 | smc_tree_abort (errno); |
||
1564 | entry = branch->entries + index; |
||
1565 | memmove (entry + 1, entry, (branch->n_entries - index) * sizeof (entry[0])); |
||
1566 | branch->n_entries += 1; |
||
1567 | return entry; |
||
1568 | } |
||
1569 | |||
1570 | static inline SmcEntry* |
||
1571 | smc_tree_branch_lookup_nearest_L (SmcBranch *branch, |
||
1572 | SmcKType key) |
||
1573 | { |
||
1574 | unsigned int n_nodes = branch->n_entries, offs = 0; |
||
1575 | SmcEntry *check = branch->entries; |
||
1576 | int cmp = 0; |
||
1577 | while (offs < n_nodes) |
||
1578 | { |
||
1579 | unsigned int i = (offs + n_nodes) >> 1; |
||
1580 | check = branch->entries + i; |
||
1581 | cmp = key < check->key ? -1 : key != check->key; |
||
1582 | if (cmp == 0) |
||
1583 | return check; /* return exact match */ |
||
1584 | else if (cmp < 0) |
||
1585 | n_nodes = i; |
||
1586 | else /* (cmp > 0) */ |
||
1587 | offs = i + 1; |
||
1588 | } |
||
1589 | /* check points at last mismatch, cmp > 0 indicates greater key */ |
||
1590 | return cmp > 0 ? check + 1 : check; /* return insertion position for inexact match */ |
||
1591 | } |
||
1592 | |||
1593 | static void |
||
1594 | smc_tree_insert (SmcKType key, |
||
1595 | SmcVType value) |
||
1596 | { |
||
1597 | unsigned int ix0, ix1; |
||
1598 | SmcEntry *entry; |
||
1599 | |||
1600 | g_mutex_lock (&smc_tree_mutex); |
||
1601 | ix0 = SMC_TRUNK_HASH (key); |
||
1602 | ix1 = SMC_BRANCH_HASH (key); |
||
1603 | if (!smc_tree_root) |
||
1604 | { |
||
1605 | smc_tree_root = calloc (SMC_TRUNK_COUNT, sizeof (smc_tree_root[0])); |
||
1606 | if (!smc_tree_root) |
||
1607 | smc_tree_abort (errno); |
||
1608 | } |
||
1609 | if (!smc_tree_root[ix0]) |
||
1610 | { |
||
1611 | smc_tree_root[ix0] = calloc (SMC_BRANCH_COUNT, sizeof (smc_tree_root[0][0])); |
||
1612 | if (!smc_tree_root[ix0]) |
||
1613 | smc_tree_abort (errno); |
||
1614 | } |
||
1615 | entry = smc_tree_branch_lookup_nearest_L (&smc_tree_root[ix0][ix1], key); |
||
1616 | if (!entry || /* need create */ |
||
1617 | entry >= smc_tree_root[ix0][ix1].entries + smc_tree_root[ix0][ix1].n_entries || /* need append */ |
||
1618 | entry->key != key) /* need insert */ |
||
1619 | entry = smc_tree_branch_grow_L (&smc_tree_root[ix0][ix1], entry - smc_tree_root[ix0][ix1].entries); |
||
1620 | entry->key = key; |
||
1621 | entry->value = value; |
||
1622 | g_mutex_unlock (&smc_tree_mutex); |
||
1623 | } |
||
1624 | |||
1625 | static gboolean |
||
1626 | smc_tree_lookup (SmcKType key, |
||
1627 | SmcVType *value_p) |
||
1628 | { |
||
1629 | SmcEntry *entry = NULL; |
||
1630 | unsigned int ix0 = SMC_TRUNK_HASH (key), ix1 = SMC_BRANCH_HASH (key); |
||
1631 | gboolean found_one = FALSE; |
||
1632 | *value_p = 0; |
||
1633 | g_mutex_lock (&smc_tree_mutex); |
||
1634 | if (smc_tree_root && smc_tree_root[ix0]) |
||
1635 | { |
||
1636 | entry = smc_tree_branch_lookup_nearest_L (&smc_tree_root[ix0][ix1], key); |
||
1637 | if (entry && |
||
1638 | entry < smc_tree_root[ix0][ix1].entries + smc_tree_root[ix0][ix1].n_entries && |
||
1639 | entry->key == key) |
||
1640 | { |
||
1641 | found_one = TRUE; |
||
1642 | *value_p = entry->value; |
||
1643 | } |
||
1644 | } |
||
1645 | g_mutex_unlock (&smc_tree_mutex); |
||
1646 | return found_one; |
||
1647 | } |
||
1648 | |||
1649 | static gboolean |
||
1650 | smc_tree_remove (SmcKType key) |
||
1651 | { |
||
1652 | unsigned int ix0 = SMC_TRUNK_HASH (key), ix1 = SMC_BRANCH_HASH (key); |
||
1653 | gboolean found_one = FALSE; |
||
1654 | g_mutex_lock (&smc_tree_mutex); |
||
1655 | if (smc_tree_root && smc_tree_root[ix0]) |
||
1656 | { |
||
1657 | SmcEntry *entry = smc_tree_branch_lookup_nearest_L (&smc_tree_root[ix0][ix1], key); |
||
1658 | if (entry && |
||
1659 | entry < smc_tree_root[ix0][ix1].entries + smc_tree_root[ix0][ix1].n_entries && |
||
1660 | entry->key == key) |
||
1661 | { |
||
1662 | unsigned int i = entry - smc_tree_root[ix0][ix1].entries; |
||
1663 | smc_tree_root[ix0][ix1].n_entries -= 1; |
||
1664 | memmove (entry, entry + 1, (smc_tree_root[ix0][ix1].n_entries - i) * sizeof (entry[0])); |
||
1665 | if (!smc_tree_root[ix0][ix1].n_entries) |
||
1666 | { |
||
1667 | /* avoid useless pressure on the memory system */ |
||
1668 | free (smc_tree_root[ix0][ix1].entries); |
||
1669 | smc_tree_root[ix0][ix1].entries = NULL; |
||
1670 | } |
||
1671 | found_one = TRUE; |
||
1672 | } |
||
1673 | } |
||
1674 | g_mutex_unlock (&smc_tree_mutex); |
||
1675 | return found_one; |
||
1676 | } |
||
1677 | |||
1678 | #ifdef G_ENABLE_DEBUG |
||
1679 | void |
||
1680 | g_slice_debug_tree_statistics (void) |
||
1681 | { |
||
1682 | g_mutex_lock (&smc_tree_mutex); |
||
1683 | if (smc_tree_root) |
||
1684 | { |
||
1685 | unsigned int i, j, t = 0, o = 0, b = 0, su = 0, ex = 0, en = 4294967295u; |
||
1686 | double tf, bf; |
||
1687 | for (i = 0; i < SMC_TRUNK_COUNT; i++) |
||
1688 | if (smc_tree_root[i]) |
||
1689 | { |
||
1690 | t++; |
||
1691 | for (j = 0; j < SMC_BRANCH_COUNT; j++) |
||
1692 | if (smc_tree_root[i][j].n_entries) |
||
1693 | { |
||
1694 | b++; |
||
1695 | su += smc_tree_root[i][j].n_entries; |
||
1696 | en = MIN (en, smc_tree_root[i][j].n_entries); |
||
1697 | ex = MAX (ex, smc_tree_root[i][j].n_entries); |
||
1698 | } |
||
1699 | else if (smc_tree_root[i][j].entries) |
||
1700 | o++; /* formerly used, now empty */ |
||
1701 | } |
||
1702 | en = b ? en : 0; |
||
1703 | tf = MAX (t, 1.0); /* max(1) to be a valid divisor */ |
||
1704 | bf = MAX (b, 1.0); /* max(1) to be a valid divisor */ |
||
1705 | fprintf (stderr, "GSlice: MemChecker: %u trunks, %u branches, %u old branches\n", t, b, o); |
||
1706 | fprintf (stderr, "GSlice: MemChecker: %f branches per trunk, %.2f%% utilization\n", |
||
1707 | b / tf, |
||
1708 | 100.0 - (SMC_BRANCH_COUNT - b / tf) / (0.01 * SMC_BRANCH_COUNT)); |
||
1709 | fprintf (stderr, "GSlice: MemChecker: %f entries per branch, %u minimum, %u maximum\n", |
||
1710 | su / bf, en, ex); |
||
1711 | } |
||
1712 | else |
||
1713 | fprintf (stderr, "GSlice: MemChecker: root=NULL\n"); |
||
1714 | g_mutex_unlock (&smc_tree_mutex); |
||
1715 | |||
1716 | /* sample statistics (beast + GSLice + 24h scripted core & GUI activity): |
||
1717 | * PID %CPU %MEM VSZ RSS COMMAND |
||
1718 | * 8887 30.3 45.8 456068 414856 beast-0.7.1 empty.bse |
||
1719 | * $ cat /proc/8887/statm # total-program-size resident-set-size shared-pages text/code data/stack library dirty-pages |
||
1720 | * 114017 103714 2354 344 0 108676 0 |
||
1721 | * $ cat /proc/8887/status |
||
1722 | * Name: beast-0.7.1 |
||
1723 | * VmSize: 456068 kB |
||
1724 | * VmLck: 0 kB |
||
1725 | * VmRSS: 414856 kB |
||
1726 | * VmData: 434620 kB |
||
1727 | * VmStk: 84 kB |
||
1728 | * VmExe: 1376 kB |
||
1729 | * VmLib: 13036 kB |
||
1730 | * VmPTE: 456 kB |
||
1731 | * Threads: 3 |
||
1732 | * (gdb) print g_slice_debug_tree_statistics () |
||
1733 | * GSlice: MemChecker: 422 trunks, 213068 branches, 0 old branches |
||
1734 | * GSlice: MemChecker: 504.900474 branches per trunk, 98.81% utilization |
||
1735 | * GSlice: MemChecker: 4.965039 entries per branch, 1 minimum, 37 maximum |
||
1736 | */ |
||
1737 | } |
||
1738 | #endif /* G_ENABLE_DEBUG */ |