/src/irssi/subprojects/glib-2.74.3/glib/gslice.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* GLIB sliced memory - fast concurrent memory chunk allocator |
2 | | * Copyright (C) 2005 Tim Janik |
3 | | * |
4 | | * SPDX-License-Identifier: LGPL-2.1-or-later |
5 | | * |
6 | | * This library is free software; you can redistribute it and/or |
7 | | * modify it under the terms of the GNU Lesser General Public |
8 | | * License as published by the Free Software Foundation; either |
9 | | * version 2.1 of the License, or (at your option) any later version. |
10 | | * |
11 | | * This library is distributed in the hope that it will be useful, |
12 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
14 | | * Lesser General Public License for more details. |
15 | | * |
16 | | * You should have received a copy of the GNU Lesser General Public |
17 | | * License along with this library; if not, see <http://www.gnu.org/licenses/>. |
18 | | */ |
19 | | /* MT safe */ |
20 | | |
21 | | #include "config.h" |
22 | | #include "glibconfig.h" |
23 | | |
24 | | #if defined(HAVE_POSIX_MEMALIGN) && !defined(_XOPEN_SOURCE) |
25 | | #define _XOPEN_SOURCE 600 /* posix_memalign() */ |
26 | | #endif |
27 | | #include <stdlib.h> /* posix_memalign() */ |
28 | | #include <string.h> |
29 | | #include <errno.h> |
30 | | |
31 | | #ifdef G_OS_UNIX |
32 | | #include <unistd.h> /* sysconf() */ |
33 | | #endif |
34 | | #ifdef G_OS_WIN32 |
35 | | #include <windows.h> |
36 | | #include <process.h> |
37 | | #endif |
38 | | |
39 | | #include <stdio.h> /* fputs */ |
40 | | |
41 | | #include "gslice.h" |
42 | | |
43 | | #include "gmain.h" |
44 | | #include "gmem.h" /* gslice.h */ |
45 | | #include "gstrfuncs.h" |
46 | | #include "gutils.h" |
47 | | #include "gtrashstack.h" |
48 | | #include "gtestutils.h" |
49 | | #include "gthread.h" |
50 | | #include "gthreadprivate.h" |
51 | | #include "glib_trace.h" |
52 | | #include "gprintf.h" |
53 | | |
54 | | #include "gvalgrind.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 retrieve the |
147 | | * thread handle. |
148 | | * - the magazine cache. allocating and freeing chunks to/from threads only |
149 | | * occurs 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 | 3.68G | #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 | 23.3k | #define NATIVE_MALLOC_PADDING P2ALIGNMENT /* per-page padding left for native malloc(3) see [1] */ |
195 | 223k | #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 | 0 | #define MIN_MAGAZINE_SIZE (4) |
198 | 23.3M | #define MAX_STAMP_COUNTER (7) /* distributes the load of gettimeofday() */ |
199 | 8 | #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 | 8 | #define MAX_SLAB_INDEX(al) (SLAB_INDEX (al, MAX_SLAB_CHUNK_SIZE (al)) + 1) |
201 | 2.04G | #define SLAB_INDEX(al, asize) ((asize) / P2ALIGNMENT - 1) /* asize must be P2ALIGNMENT aligned */ |
202 | 1.63G | #define SLAB_CHUNK_SIZE(al, ix) (((ix) + 1) * P2ALIGNMENT) |
203 | 111k | #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 | 2.04G | #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 | 23.5M | #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 functions --- */ |
297 | | void |
298 | | g_slice_set_config (GSliceConfig ckey, |
299 | | gint64 value) |
300 | 0 | { |
301 | 0 | g_return_if_fail (sys_page_size == 0); |
302 | 0 | switch (ckey) |
303 | 0 | { |
304 | 0 | case G_SLICE_CONFIG_ALWAYS_MALLOC: |
305 | 0 | slice_config.always_malloc = value != 0; |
306 | 0 | break; |
307 | 0 | case G_SLICE_CONFIG_BYPASS_MAGAZINES: |
308 | 0 | slice_config.bypass_magazines = value != 0; |
309 | 0 | break; |
310 | 0 | case G_SLICE_CONFIG_WORKING_SET_MSECS: |
311 | 0 | slice_config.working_set_msecs = value; |
312 | 0 | break; |
313 | 0 | case G_SLICE_CONFIG_COLOR_INCREMENT: |
314 | 0 | slice_config.color_increment = value; |
315 | 0 | break; |
316 | 0 | default: ; |
317 | 0 | } |
318 | 0 | } |
319 | | |
320 | | gint64 |
321 | | g_slice_get_config (GSliceConfig ckey) |
322 | 0 | { |
323 | 0 | switch (ckey) |
324 | 0 | { |
325 | 0 | case G_SLICE_CONFIG_ALWAYS_MALLOC: |
326 | 0 | return slice_config.always_malloc; |
327 | 0 | case G_SLICE_CONFIG_BYPASS_MAGAZINES: |
328 | 0 | return slice_config.bypass_magazines; |
329 | 0 | case G_SLICE_CONFIG_WORKING_SET_MSECS: |
330 | 0 | return slice_config.working_set_msecs; |
331 | 0 | case G_SLICE_CONFIG_CHUNK_SIZES: |
332 | 0 | return MAX_SLAB_INDEX (allocator); |
333 | 0 | case G_SLICE_CONFIG_COLOR_INCREMENT: |
334 | 0 | return slice_config.color_increment; |
335 | 0 | default: |
336 | 0 | return 0; |
337 | 0 | } |
338 | 0 | } |
339 | | |
340 | | gint64* |
341 | | g_slice_get_config_state (GSliceConfig ckey, |
342 | | gint64 address, |
343 | | guint *n_values) |
344 | 0 | { |
345 | 0 | guint i = 0; |
346 | 0 | g_return_val_if_fail (n_values != NULL, NULL); |
347 | 0 | *n_values = 0; |
348 | 0 | switch (ckey) |
349 | 0 | { |
350 | 0 | gint64 array[64]; |
351 | 0 | case G_SLICE_CONFIG_CONTENTION_COUNTER: |
352 | 0 | array[i++] = SLAB_CHUNK_SIZE (allocator, address); |
353 | 0 | array[i++] = allocator->contention_counters[address]; |
354 | 0 | array[i++] = allocator_get_magazine_threshold (allocator, address); |
355 | 0 | *n_values = i; |
356 | 0 | return g_memdup2 (array, sizeof (array[0]) * *n_values); |
357 | 0 | default: |
358 | 0 | return NULL; |
359 | 0 | } |
360 | 0 | } |
361 | | |
362 | | static void |
363 | | slice_config_init (SliceConfig *config) |
364 | 8 | { |
365 | 8 | const gchar *val; |
366 | 8 | gchar *val_allocated = NULL; |
367 | | |
368 | 8 | *config = slice_config; |
369 | | |
370 | | /* Note that the empty string (`G_SLICE=""`) is treated differently from the |
371 | | * envvar being unset. In the latter case, we also check whether running under |
372 | | * valgrind. */ |
373 | 8 | #ifndef G_OS_WIN32 |
374 | 8 | val = g_getenv ("G_SLICE"); |
375 | | #else |
376 | | /* The win32 implementation of g_getenv() has to do UTF-8 ↔ UTF-16 conversions |
377 | | * which use the slice allocator, leading to deadlock. Use a simple in-place |
378 | | * implementation here instead. |
379 | | * |
380 | | * Ignore references to other environment variables: only support values which |
381 | | * are a combination of always-malloc and debug-blocks. */ |
382 | | { |
383 | | |
384 | | wchar_t wvalue[128]; /* at least big enough for `always-malloc,debug-blocks` */ |
385 | | gsize len; |
386 | | |
387 | | len = GetEnvironmentVariableW (L"G_SLICE", wvalue, G_N_ELEMENTS (wvalue)); |
388 | | |
389 | | if (len == 0) |
390 | | { |
391 | | if (GetLastError () == ERROR_ENVVAR_NOT_FOUND) |
392 | | val = NULL; |
393 | | else |
394 | | val = ""; |
395 | | } |
396 | | else if (len >= G_N_ELEMENTS (wvalue)) |
397 | | { |
398 | | /* @wvalue isn’t big enough. Give up. */ |
399 | | g_warning ("Unsupported G_SLICE value"); |
400 | | val = NULL; |
401 | | } |
402 | | else |
403 | | { |
404 | | /* it’s safe to use g_utf16_to_utf8() here as it only allocates using |
405 | | * malloc() rather than GSlice */ |
406 | | val = val_allocated = g_utf16_to_utf8 (wvalue, -1, NULL, NULL, NULL); |
407 | | } |
408 | | |
409 | | } |
410 | | #endif /* G_OS_WIN32 */ |
411 | | |
412 | 8 | if (val != NULL) |
413 | 0 | { |
414 | 0 | gint flags; |
415 | 0 | const GDebugKey keys[] = { |
416 | 0 | { "always-malloc", 1 << 0 }, |
417 | 0 | { "debug-blocks", 1 << 1 }, |
418 | 0 | }; |
419 | |
|
420 | 0 | flags = g_parse_debug_string (val, keys, G_N_ELEMENTS (keys)); |
421 | 0 | if (flags & (1 << 0)) |
422 | 0 | config->always_malloc = TRUE; |
423 | 0 | if (flags & (1 << 1)) |
424 | 0 | config->debug_blocks = TRUE; |
425 | 0 | } |
426 | 8 | else |
427 | 8 | { |
428 | | /* G_SLICE was not specified, so check if valgrind is running and |
429 | | * disable ourselves if it is. |
430 | | * |
431 | | * This way it's possible to force gslice to be enabled under |
432 | | * valgrind just by setting G_SLICE to the empty string. |
433 | | */ |
434 | 8 | #ifdef ENABLE_VALGRIND |
435 | 8 | if (RUNNING_ON_VALGRIND) |
436 | 0 | config->always_malloc = TRUE; |
437 | 8 | #endif |
438 | 8 | } |
439 | | |
440 | 8 | g_free (val_allocated); |
441 | 8 | } |
442 | | |
443 | | static void |
444 | | g_slice_init_nomessage (void) |
445 | 8 | { |
446 | | /* we may not use g_error() or friends here */ |
447 | 8 | mem_assert (sys_page_size == 0); |
448 | 8 | mem_assert (MIN_MAGAZINE_SIZE >= 4); |
449 | | |
450 | | #ifdef G_OS_WIN32 |
451 | | { |
452 | | SYSTEM_INFO system_info; |
453 | | GetSystemInfo (&system_info); |
454 | | sys_page_size = system_info.dwPageSize; |
455 | | } |
456 | | #else |
457 | 8 | sys_page_size = sysconf (_SC_PAGESIZE); /* = sysconf (_SC_PAGE_SIZE); = getpagesize(); */ |
458 | 8 | #endif |
459 | 8 | mem_assert (sys_page_size >= 2 * LARGEALIGNMENT); |
460 | 8 | mem_assert ((sys_page_size & (sys_page_size - 1)) == 0); |
461 | 8 | slice_config_init (&allocator->config); |
462 | 8 | allocator->min_page_size = sys_page_size; |
463 | 8 | #if HAVE_POSIX_MEMALIGN || HAVE_MEMALIGN |
464 | | /* allow allocation of pages up to 8KB (with 8KB alignment). |
465 | | * this is useful because many medium to large sized structures |
466 | | * fit less than 8 times (see [4]) into 4KB pages. |
467 | | * we allow very small page sizes here, to reduce wastage in |
468 | | * threads if only small allocations are required (this does |
469 | | * bear the risk of increasing allocation times and fragmentation |
470 | | * though). |
471 | | */ |
472 | 8 | allocator->min_page_size = MAX (allocator->min_page_size, 4096); |
473 | 8 | allocator->max_page_size = MAX (allocator->min_page_size, 8192); |
474 | 8 | allocator->min_page_size = MIN (allocator->min_page_size, 128); |
475 | | #else |
476 | | /* we can only align to system page size */ |
477 | | allocator->max_page_size = sys_page_size; |
478 | | #endif |
479 | 8 | if (allocator->config.always_malloc) |
480 | 0 | { |
481 | 0 | allocator->contention_counters = NULL; |
482 | 0 | allocator->magazines = NULL; |
483 | 0 | allocator->slab_stack = NULL; |
484 | 0 | } |
485 | 8 | else |
486 | 8 | { |
487 | 8 | allocator->contention_counters = g_new0 (guint, MAX_SLAB_INDEX (allocator)); |
488 | 8 | allocator->magazines = g_new0 (ChunkLink*, MAX_SLAB_INDEX (allocator)); |
489 | 8 | allocator->slab_stack = g_new0 (SlabInfo*, MAX_SLAB_INDEX (allocator)); |
490 | 8 | } |
491 | | |
492 | 8 | allocator->mutex_counter = 0; |
493 | 8 | allocator->stamp_counter = MAX_STAMP_COUNTER; /* force initial update */ |
494 | 8 | allocator->last_stamp = 0; |
495 | 8 | allocator->color_accu = 0; |
496 | 8 | magazine_cache_update_stamp(); |
497 | | /* values cached for performance reasons */ |
498 | 8 | allocator->max_slab_chunk_size_for_magazine_cache = MAX_SLAB_CHUNK_SIZE (allocator); |
499 | 8 | if (allocator->config.always_malloc || allocator->config.bypass_magazines) |
500 | 0 | allocator->max_slab_chunk_size_for_magazine_cache = 0; /* non-optimized cases */ |
501 | 8 | } |
502 | | |
503 | | static inline guint |
504 | | allocator_categorize (gsize aligned_chunk_size) |
505 | 2.04G | { |
506 | | /* speed up the likely path */ |
507 | 2.04G | if (G_LIKELY (aligned_chunk_size && aligned_chunk_size <= allocator->max_slab_chunk_size_for_magazine_cache)) |
508 | 2.04G | return 1; /* use magazine cache */ |
509 | | |
510 | 0 | if (!allocator->config.always_malloc && |
511 | 0 | aligned_chunk_size && |
512 | 0 | aligned_chunk_size <= MAX_SLAB_CHUNK_SIZE (allocator)) |
513 | 0 | { |
514 | 0 | if (allocator->config.bypass_magazines) |
515 | 0 | return 2; /* use slab allocator, see [2] */ |
516 | 0 | return 1; /* use magazine cache */ |
517 | 0 | } |
518 | 0 | return 0; /* use malloc() */ |
519 | 0 | } |
520 | | |
521 | | static inline void |
522 | | g_mutex_lock_a (GMutex *mutex, |
523 | | guint *contention_counter) |
524 | 23.3M | { |
525 | 23.3M | gboolean contention = FALSE; |
526 | 23.3M | if (!g_mutex_trylock (mutex)) |
527 | 0 | { |
528 | 0 | g_mutex_lock (mutex); |
529 | 0 | contention = TRUE; |
530 | 0 | } |
531 | 23.3M | if (contention) |
532 | 0 | { |
533 | 0 | allocator->mutex_counter++; |
534 | 0 | if (allocator->mutex_counter >= 1) /* quickly adapt to contention */ |
535 | 0 | { |
536 | 0 | allocator->mutex_counter = 0; |
537 | 0 | *contention_counter = MIN (*contention_counter + 1, MAX_MAGAZINE_SIZE); |
538 | 0 | } |
539 | 0 | } |
540 | 23.3M | else /* !contention */ |
541 | 23.3M | { |
542 | 23.3M | allocator->mutex_counter--; |
543 | 23.3M | if (allocator->mutex_counter < -11) /* moderately recover magazine sizes */ |
544 | 1.94M | { |
545 | 1.94M | allocator->mutex_counter = 0; |
546 | 1.94M | *contention_counter = MAX (*contention_counter, 1) - 1; |
547 | 1.94M | } |
548 | 23.3M | } |
549 | 23.3M | } |
550 | | |
551 | | static inline ThreadMemory* |
552 | | thread_memory_from_self (void) |
553 | 2.04G | { |
554 | 2.04G | ThreadMemory *tmem = g_private_get (&private_thread_memory); |
555 | 2.04G | if (G_UNLIKELY (!tmem)) |
556 | 8 | { |
557 | 8 | static GMutex init_mutex; |
558 | 8 | guint n_magazines; |
559 | | |
560 | 8 | g_mutex_lock (&init_mutex); |
561 | 8 | if G_UNLIKELY (sys_page_size == 0) |
562 | 8 | g_slice_init_nomessage (); |
563 | 8 | g_mutex_unlock (&init_mutex); |
564 | | |
565 | 8 | n_magazines = MAX_SLAB_INDEX (allocator); |
566 | 8 | tmem = g_private_set_alloc0 (&private_thread_memory, sizeof (ThreadMemory) + sizeof (Magazine) * 2 * n_magazines); |
567 | 8 | tmem->magazine1 = (Magazine*) (tmem + 1); |
568 | 8 | tmem->magazine2 = &tmem->magazine1[n_magazines]; |
569 | 8 | } |
570 | 2.04G | return tmem; |
571 | 2.04G | } |
572 | | |
573 | | static inline ChunkLink* |
574 | | magazine_chain_pop_head (ChunkLink **magazine_chunks) |
575 | 1.68G | { |
576 | | /* magazine chains are linked via ChunkLink->next. |
577 | | * each ChunkLink->data of the toplevel chain may point to a subchain, |
578 | | * linked via ChunkLink->next. ChunkLink->data of the subchains just |
579 | | * contains uninitialized junk. |
580 | | */ |
581 | 1.68G | ChunkLink *chunk = (*magazine_chunks)->data; |
582 | 1.68G | if (G_UNLIKELY (chunk)) |
583 | 0 | { |
584 | | /* allocating from freed list */ |
585 | 0 | (*magazine_chunks)->data = chunk->next; |
586 | 0 | } |
587 | 1.68G | else |
588 | 1.68G | { |
589 | 1.68G | chunk = *magazine_chunks; |
590 | 1.68G | *magazine_chunks = chunk->next; |
591 | 1.68G | } |
592 | 1.68G | return chunk; |
593 | 1.68G | } |
594 | | |
595 | | #if 0 /* useful for debugging */ |
596 | | static guint |
597 | | magazine_count (ChunkLink *head) |
598 | | { |
599 | | guint count = 0; |
600 | | if (!head) |
601 | | return 0; |
602 | | while (head) |
603 | | { |
604 | | ChunkLink *child = head->data; |
605 | | count += 1; |
606 | | for (child = head->data; child; child = child->next) |
607 | | count += 1; |
608 | | head = head->next; |
609 | | } |
610 | | return count; |
611 | | } |
612 | | #endif |
613 | | |
614 | | static inline gsize |
615 | | allocator_get_magazine_threshold (Allocator *local_allocator, |
616 | | guint ix) |
617 | 1.63G | { |
618 | | /* the magazine size calculated here has a lower bound of MIN_MAGAZINE_SIZE, |
619 | | * which is required by the implementation. also, for moderately sized chunks |
620 | | * (say >= 64 bytes), magazine sizes shouldn't be much smaller then the number |
621 | | * of chunks available per page/2 to avoid excessive traffic in the magazine |
622 | | * cache for small to medium sized structures. |
623 | | * the upper bound of the magazine size is effectively provided by |
624 | | * MAX_MAGAZINE_SIZE. for larger chunks, this number is scaled down so that |
625 | | * the content of a single magazine doesn't exceed ca. 16KB. |
626 | | */ |
627 | 1.63G | gsize chunk_size = SLAB_CHUNK_SIZE (local_allocator, ix); |
628 | 1.63G | guint threshold = MAX (MIN_MAGAZINE_SIZE, local_allocator->max_page_size / MAX (5 * chunk_size, 5 * 32)); |
629 | 1.63G | guint contention_counter = local_allocator->contention_counters[ix]; |
630 | 1.63G | if (G_UNLIKELY (contention_counter)) /* single CPU bias */ |
631 | 0 | { |
632 | | /* adapt contention counter thresholds to chunk sizes */ |
633 | 0 | contention_counter = contention_counter * 64 / chunk_size; |
634 | 0 | threshold = MAX (threshold, contention_counter); |
635 | 0 | } |
636 | 1.63G | return threshold; |
637 | 1.63G | } |
638 | | |
639 | | /* --- magazine cache --- */ |
640 | | static inline void |
641 | | magazine_cache_update_stamp (void) |
642 | 23.3M | { |
643 | 23.3M | if (allocator->stamp_counter >= MAX_STAMP_COUNTER) |
644 | 2.92M | { |
645 | 2.92M | gint64 now_us = g_get_real_time (); |
646 | 2.92M | allocator->last_stamp = now_us / 1000; /* milli seconds */ |
647 | 2.92M | allocator->stamp_counter = 0; |
648 | 2.92M | } |
649 | 20.4M | else |
650 | 20.4M | allocator->stamp_counter++; |
651 | 23.3M | } |
652 | | |
653 | | static inline ChunkLink* |
654 | | magazine_chain_prepare_fields (ChunkLink *magazine_chunks) |
655 | 23.3M | { |
656 | 23.3M | ChunkLink *chunk1; |
657 | 23.3M | ChunkLink *chunk2; |
658 | 23.3M | ChunkLink *chunk3; |
659 | 23.3M | ChunkLink *chunk4; |
660 | | /* checked upon initialization: mem_assert (MIN_MAGAZINE_SIZE >= 4); */ |
661 | | /* ensure a magazine with at least 4 unused data pointers */ |
662 | 23.3M | chunk1 = magazine_chain_pop_head (&magazine_chunks); |
663 | 23.3M | chunk2 = magazine_chain_pop_head (&magazine_chunks); |
664 | 23.3M | chunk3 = magazine_chain_pop_head (&magazine_chunks); |
665 | 23.3M | chunk4 = magazine_chain_pop_head (&magazine_chunks); |
666 | 23.3M | chunk4->next = magazine_chunks; |
667 | 23.3M | chunk3->next = chunk4; |
668 | 23.3M | chunk2->next = chunk3; |
669 | 23.3M | chunk1->next = chunk2; |
670 | 23.3M | return chunk1; |
671 | 23.3M | } |
672 | | |
673 | | /* access the first 3 fields of a specially prepared magazine chain */ |
674 | 163M | #define magazine_chain_prev(mc) ((mc)->data) |
675 | 46.7M | #define magazine_chain_stamp(mc) ((mc)->next->data) |
676 | | #define magazine_chain_uint_stamp(mc) GPOINTER_TO_UINT ((mc)->next->data) |
677 | 116M | #define magazine_chain_next(mc) ((mc)->next->next->data) |
678 | 70.1M | #define magazine_chain_count(mc) ((mc)->next->next->next->data) |
679 | | |
680 | | static void |
681 | | magazine_cache_trim (Allocator *local_allocator, |
682 | | guint ix, |
683 | | guint stamp) |
684 | 23.3M | { |
685 | | /* g_mutex_lock (local_allocator->mutex); done by caller */ |
686 | | /* trim magazine cache from tail */ |
687 | 23.3M | ChunkLink *current = magazine_chain_prev (local_allocator->magazines[ix]); |
688 | 23.3M | ChunkLink *trash = NULL; |
689 | 23.3M | while (!G_APPROX_VALUE (stamp, magazine_chain_uint_stamp (current), |
690 | 23.3M | local_allocator->config.working_set_msecs)) |
691 | 2.07k | { |
692 | | /* unlink */ |
693 | 2.07k | ChunkLink *prev = magazine_chain_prev (current); |
694 | 2.07k | ChunkLink *next = magazine_chain_next (current); |
695 | 2.07k | magazine_chain_next (prev) = next; |
696 | 2.07k | magazine_chain_prev (next) = prev; |
697 | | /* clear special fields, put on trash stack */ |
698 | 2.07k | magazine_chain_next (current) = NULL; |
699 | 2.07k | magazine_chain_count (current) = NULL; |
700 | 2.07k | magazine_chain_stamp (current) = NULL; |
701 | 2.07k | magazine_chain_prev (current) = trash; |
702 | 2.07k | trash = current; |
703 | | /* fixup list head if required */ |
704 | 2.07k | if (current == local_allocator->magazines[ix]) |
705 | 0 | { |
706 | 0 | local_allocator->magazines[ix] = NULL; |
707 | 0 | break; |
708 | 0 | } |
709 | 2.07k | current = prev; |
710 | 2.07k | } |
711 | 23.3M | g_mutex_unlock (&local_allocator->magazine_mutex); |
712 | | /* free trash */ |
713 | 23.3M | if (trash) |
714 | 55 | { |
715 | 55 | const gsize chunk_size = SLAB_CHUNK_SIZE (local_allocator, ix); |
716 | 55 | g_mutex_lock (&local_allocator->slab_mutex); |
717 | 2.13k | while (trash) |
718 | 2.07k | { |
719 | 2.07k | current = trash; |
720 | 2.07k | trash = magazine_chain_prev (current); |
721 | 2.07k | magazine_chain_prev (current) = NULL; /* clear special field */ |
722 | 90.4k | while (current) |
723 | 88.3k | { |
724 | 88.3k | ChunkLink *chunk = magazine_chain_pop_head (¤t); |
725 | 88.3k | slab_allocator_free_chunk (chunk_size, chunk); |
726 | 88.3k | } |
727 | 2.07k | } |
728 | 55 | g_mutex_unlock (&local_allocator->slab_mutex); |
729 | 55 | } |
730 | 23.3M | } |
731 | | |
732 | | static void |
733 | | magazine_cache_push_magazine (guint ix, |
734 | | ChunkLink *magazine_chunks, |
735 | | gsize count) /* must be >= MIN_MAGAZINE_SIZE */ |
736 | 23.3M | { |
737 | 23.3M | ChunkLink *current = magazine_chain_prepare_fields (magazine_chunks); |
738 | 23.3M | ChunkLink *next, *prev; |
739 | 23.3M | g_mutex_lock (&allocator->magazine_mutex); |
740 | | /* add magazine at head */ |
741 | 23.3M | next = allocator->magazines[ix]; |
742 | 23.3M | if (next) |
743 | 23.3M | prev = magazine_chain_prev (next); |
744 | 85.1k | else |
745 | 85.1k | next = prev = current; |
746 | 23.3M | magazine_chain_next (prev) = current; |
747 | 23.3M | magazine_chain_prev (next) = current; |
748 | 23.3M | magazine_chain_prev (current) = prev; |
749 | 23.3M | magazine_chain_next (current) = next; |
750 | 23.3M | magazine_chain_count (current) = (gpointer) count; |
751 | | /* stamp magazine */ |
752 | 23.3M | magazine_cache_update_stamp(); |
753 | 23.3M | magazine_chain_stamp (current) = GUINT_TO_POINTER (allocator->last_stamp); |
754 | 23.3M | allocator->magazines[ix] = current; |
755 | | /* free old magazines beyond a certain threshold */ |
756 | 23.3M | magazine_cache_trim (allocator, ix, allocator->last_stamp); |
757 | | /* g_mutex_unlock (allocator->mutex); was done by magazine_cache_trim() */ |
758 | 23.3M | } |
759 | | |
760 | | static ChunkLink* |
761 | | magazine_cache_pop_magazine (guint ix, |
762 | | gsize *countp) |
763 | 23.3M | { |
764 | 23.3M | g_mutex_lock_a (&allocator->magazine_mutex, &allocator->contention_counters[ix]); |
765 | 23.3M | if (!allocator->magazines[ix]) |
766 | 9.73k | { |
767 | 9.73k | guint magazine_threshold = allocator_get_magazine_threshold (allocator, ix); |
768 | 9.73k | gsize i, chunk_size = SLAB_CHUNK_SIZE (allocator, ix); |
769 | 9.73k | ChunkLink *chunk, *head; |
770 | 9.73k | g_mutex_unlock (&allocator->magazine_mutex); |
771 | 9.73k | g_mutex_lock (&allocator->slab_mutex); |
772 | 9.73k | head = slab_allocator_alloc_chunk (chunk_size); |
773 | 9.73k | head->data = NULL; |
774 | 9.73k | chunk = head; |
775 | 312k | for (i = 1; i < magazine_threshold; i++) |
776 | 302k | { |
777 | 302k | chunk->next = slab_allocator_alloc_chunk (chunk_size); |
778 | 302k | chunk = chunk->next; |
779 | 302k | chunk->data = NULL; |
780 | 302k | } |
781 | 9.73k | chunk->next = NULL; |
782 | 9.73k | g_mutex_unlock (&allocator->slab_mutex); |
783 | 9.73k | *countp = i; |
784 | 9.73k | return head; |
785 | 9.73k | } |
786 | 23.3M | else |
787 | 23.3M | { |
788 | 23.3M | ChunkLink *current = allocator->magazines[ix]; |
789 | 23.3M | ChunkLink *prev = magazine_chain_prev (current); |
790 | 23.3M | ChunkLink *next = magazine_chain_next (current); |
791 | | /* unlink */ |
792 | 23.3M | magazine_chain_next (prev) = next; |
793 | 23.3M | magazine_chain_prev (next) = prev; |
794 | 23.3M | allocator->magazines[ix] = next == current ? NULL : next; |
795 | 23.3M | g_mutex_unlock (&allocator->magazine_mutex); |
796 | | /* clear special fields and hand out */ |
797 | 23.3M | *countp = (gsize) magazine_chain_count (current); |
798 | 23.3M | magazine_chain_prev (current) = NULL; |
799 | 23.3M | magazine_chain_next (current) = NULL; |
800 | 23.3M | magazine_chain_count (current) = NULL; |
801 | 23.3M | magazine_chain_stamp (current) = NULL; |
802 | 23.3M | return current; |
803 | 23.3M | } |
804 | 23.3M | } |
805 | | |
806 | | /* --- thread magazines --- */ |
807 | | static void |
808 | | private_thread_memory_cleanup (gpointer data) |
809 | 0 | { |
810 | 0 | ThreadMemory *tmem = data; |
811 | 0 | const guint n_magazines = MAX_SLAB_INDEX (allocator); |
812 | 0 | guint ix; |
813 | 0 | for (ix = 0; ix < n_magazines; ix++) |
814 | 0 | { |
815 | 0 | Magazine *mags[2]; |
816 | 0 | guint j; |
817 | 0 | mags[0] = &tmem->magazine1[ix]; |
818 | 0 | mags[1] = &tmem->magazine2[ix]; |
819 | 0 | for (j = 0; j < 2; j++) |
820 | 0 | { |
821 | 0 | Magazine *mag = mags[j]; |
822 | 0 | if (mag->count >= MIN_MAGAZINE_SIZE) |
823 | 0 | magazine_cache_push_magazine (ix, mag->chunks, mag->count); |
824 | 0 | else |
825 | 0 | { |
826 | 0 | const gsize chunk_size = SLAB_CHUNK_SIZE (allocator, ix); |
827 | 0 | g_mutex_lock (&allocator->slab_mutex); |
828 | 0 | while (mag->chunks) |
829 | 0 | { |
830 | 0 | ChunkLink *chunk = magazine_chain_pop_head (&mag->chunks); |
831 | 0 | slab_allocator_free_chunk (chunk_size, chunk); |
832 | 0 | } |
833 | 0 | g_mutex_unlock (&allocator->slab_mutex); |
834 | 0 | } |
835 | 0 | } |
836 | 0 | } |
837 | 0 | g_free (tmem); |
838 | 0 | } |
839 | | |
840 | | static void |
841 | | thread_memory_magazine1_reload (ThreadMemory *tmem, |
842 | | guint ix) |
843 | 23.3M | { |
844 | 23.3M | Magazine *mag = &tmem->magazine1[ix]; |
845 | 23.3M | mem_assert (mag->chunks == NULL); /* ensure that we may reset mag->count */ |
846 | 23.3M | mag->count = 0; |
847 | 23.3M | mag->chunks = magazine_cache_pop_magazine (ix, &mag->count); |
848 | 23.3M | } |
849 | | |
850 | | static void |
851 | | thread_memory_magazine2_unload (ThreadMemory *tmem, |
852 | | guint ix) |
853 | 23.3M | { |
854 | 23.3M | Magazine *mag = &tmem->magazine2[ix]; |
855 | 23.3M | magazine_cache_push_magazine (ix, mag->chunks, mag->count); |
856 | 23.3M | mag->chunks = NULL; |
857 | 23.3M | mag->count = 0; |
858 | 23.3M | } |
859 | | |
860 | | static inline void |
861 | | thread_memory_swap_magazines (ThreadMemory *tmem, |
862 | | guint ix) |
863 | 73.7M | { |
864 | 73.7M | Magazine xmag = tmem->magazine1[ix]; |
865 | 73.7M | tmem->magazine1[ix] = tmem->magazine2[ix]; |
866 | 73.7M | tmem->magazine2[ix] = xmag; |
867 | 73.7M | } |
868 | | |
869 | | static inline gboolean |
870 | | thread_memory_magazine1_is_empty (ThreadMemory *tmem, |
871 | | guint ix) |
872 | 1.61G | { |
873 | 1.61G | return tmem->magazine1[ix].chunks == NULL; |
874 | 1.61G | } |
875 | | |
876 | | static inline gboolean |
877 | | thread_memory_magazine2_is_full (ThreadMemory *tmem, |
878 | | guint ix) |
879 | 1.63G | { |
880 | 1.63G | return tmem->magazine2[ix].count >= allocator_get_magazine_threshold (allocator, ix); |
881 | 1.63G | } |
882 | | |
883 | | static inline gpointer |
884 | | thread_memory_magazine1_alloc (ThreadMemory *tmem, |
885 | | guint ix) |
886 | 1.58G | { |
887 | 1.58G | Magazine *mag = &tmem->magazine1[ix]; |
888 | 1.58G | ChunkLink *chunk = magazine_chain_pop_head (&mag->chunks); |
889 | 1.58G | if (G_LIKELY (mag->count > 0)) |
890 | 1.58G | mag->count--; |
891 | 1.58G | return chunk; |
892 | 1.58G | } |
893 | | |
894 | | static inline void |
895 | | thread_memory_magazine2_free (ThreadMemory *tmem, |
896 | | guint ix, |
897 | | gpointer mem) |
898 | 1.58G | { |
899 | 1.58G | Magazine *mag = &tmem->magazine2[ix]; |
900 | 1.58G | ChunkLink *chunk = mem; |
901 | 1.58G | chunk->data = NULL; |
902 | 1.58G | chunk->next = mag->chunks; |
903 | 1.58G | mag->chunks = chunk; |
904 | 1.58G | mag->count++; |
905 | 1.58G | } |
906 | | |
907 | | /* --- API functions --- */ |
908 | | |
909 | | /** |
910 | | * g_slice_new: |
911 | | * @type: the type to allocate, typically a structure name |
912 | | * |
913 | | * A convenience macro to allocate a block of memory from the |
914 | | * slice allocator. |
915 | | * |
916 | | * It calls g_slice_alloc() with `sizeof (@type)` and casts the |
917 | | * returned pointer to a pointer of the given type, avoiding a type |
918 | | * cast in the source code. Note that the underlying slice allocation |
919 | | * mechanism can be changed with the [`G_SLICE=always-malloc`][G_SLICE] |
920 | | * environment variable. |
921 | | * |
922 | | * This can never return %NULL as the minimum allocation size from |
923 | | * `sizeof (@type)` is 1 byte. |
924 | | * |
925 | | * Returns: (not nullable): a pointer to the allocated block, cast to a pointer |
926 | | * to @type |
927 | | * |
928 | | * Since: 2.10 |
929 | | */ |
930 | | |
931 | | /** |
932 | | * g_slice_new0: |
933 | | * @type: the type to allocate, typically a structure name |
934 | | * |
935 | | * A convenience macro to allocate a block of memory from the |
936 | | * slice allocator and set the memory to 0. |
937 | | * |
938 | | * It calls g_slice_alloc0() with `sizeof (@type)` |
939 | | * and casts the returned pointer to a pointer of the given type, |
940 | | * avoiding a type cast in the source code. |
941 | | * Note that the underlying slice allocation mechanism can |
942 | | * be changed with the [`G_SLICE=always-malloc`][G_SLICE] |
943 | | * environment variable. |
944 | | * |
945 | | * This can never return %NULL as the minimum allocation size from |
946 | | * `sizeof (@type)` is 1 byte. |
947 | | * |
948 | | * Returns: (not nullable): a pointer to the allocated block, cast to a pointer |
949 | | * to @type |
950 | | * |
951 | | * Since: 2.10 |
952 | | */ |
953 | | |
954 | | /** |
955 | | * g_slice_dup: |
956 | | * @type: the type to duplicate, typically a structure name |
957 | | * @mem: (not nullable): the memory to copy into the allocated block |
958 | | * |
959 | | * A convenience macro to duplicate a block of memory using |
960 | | * the slice allocator. |
961 | | * |
962 | | * It calls g_slice_copy() with `sizeof (@type)` |
963 | | * and casts the returned pointer to a pointer of the given type, |
964 | | * avoiding a type cast in the source code. |
965 | | * Note that the underlying slice allocation mechanism can |
966 | | * be changed with the [`G_SLICE=always-malloc`][G_SLICE] |
967 | | * environment variable. |
968 | | * |
969 | | * This can never return %NULL. |
970 | | * |
971 | | * Returns: (not nullable): a pointer to the allocated block, cast to a pointer |
972 | | * to @type |
973 | | * |
974 | | * Since: 2.14 |
975 | | */ |
976 | | |
977 | | /** |
978 | | * g_slice_free: |
979 | | * @type: the type of the block to free, typically a structure name |
980 | | * @mem: a pointer to the block to free |
981 | | * |
982 | | * A convenience macro to free a block of memory that has |
983 | | * been allocated from the slice allocator. |
984 | | * |
985 | | * It calls g_slice_free1() using `sizeof (type)` |
986 | | * as the block size. |
987 | | * Note that the exact release behaviour can be changed with the |
988 | | * [`G_DEBUG=gc-friendly`][G_DEBUG] environment variable, also see |
989 | | * [`G_SLICE`][G_SLICE] for related debugging options. |
990 | | * |
991 | | * If @mem is %NULL, this macro does nothing. |
992 | | * |
993 | | * Since: 2.10 |
994 | | */ |
995 | | |
996 | | /** |
997 | | * g_slice_free_chain: |
998 | | * @type: the type of the @mem_chain blocks |
999 | | * @mem_chain: a pointer to the first block of the chain |
1000 | | * @next: the field name of the next pointer in @type |
1001 | | * |
1002 | | * Frees a linked list of memory blocks of structure type @type. |
1003 | | * |
1004 | | * The memory blocks must be equal-sized, allocated via |
1005 | | * g_slice_alloc() or g_slice_alloc0() and linked together by |
1006 | | * a @next pointer (similar to #GSList). The name of the |
1007 | | * @next field in @type is passed as third argument. |
1008 | | * Note that the exact release behaviour can be changed with the |
1009 | | * [`G_DEBUG=gc-friendly`][G_DEBUG] environment variable, also see |
1010 | | * [`G_SLICE`][G_SLICE] for related debugging options. |
1011 | | * |
1012 | | * If @mem_chain is %NULL, this function does nothing. |
1013 | | * |
1014 | | * Since: 2.10 |
1015 | | */ |
1016 | | |
1017 | | /** |
1018 | | * g_slice_alloc: |
1019 | | * @block_size: the number of bytes to allocate |
1020 | | * |
1021 | | * Allocates a block of memory from the slice allocator. |
1022 | | * |
1023 | | * The block address handed out can be expected to be aligned |
1024 | | * to at least `1 * sizeof (void*)`, though in general slices |
1025 | | * are `2 * sizeof (void*)` bytes aligned; if a `malloc()` |
1026 | | * fallback implementation is used instead, the alignment may |
1027 | | * be reduced in a libc dependent fashion. |
1028 | | * |
1029 | | * Note that the underlying slice allocation mechanism can |
1030 | | * be changed with the [`G_SLICE=always-malloc`][G_SLICE] |
1031 | | * environment variable. |
1032 | | * |
1033 | | * Returns: a pointer to the allocated memory block, which will |
1034 | | * be %NULL if and only if @mem_size is 0 |
1035 | | * |
1036 | | * Since: 2.10 |
1037 | | */ |
1038 | | gpointer |
1039 | | g_slice_alloc (gsize mem_size) |
1040 | 1.58G | { |
1041 | 1.58G | ThreadMemory *tmem; |
1042 | 1.58G | gsize chunk_size; |
1043 | 1.58G | gpointer mem; |
1044 | 1.58G | guint acat; |
1045 | | |
1046 | | /* This gets the private structure for this thread. If the private |
1047 | | * structure does not yet exist, it is created. |
1048 | | * |
1049 | | * This has a side effect of causing GSlice to be initialised, so it |
1050 | | * must come first. |
1051 | | */ |
1052 | 1.58G | tmem = thread_memory_from_self (); |
1053 | | |
1054 | 1.58G | chunk_size = P2ALIGN (mem_size); |
1055 | 1.58G | acat = allocator_categorize (chunk_size); |
1056 | 1.58G | if (G_LIKELY (acat == 1)) /* allocate through magazine layer */ |
1057 | 1.58G | { |
1058 | 1.58G | guint ix = SLAB_INDEX (allocator, chunk_size); |
1059 | 1.58G | if (G_UNLIKELY (thread_memory_magazine1_is_empty (tmem, ix))) |
1060 | 25.4M | { |
1061 | 25.4M | thread_memory_swap_magazines (tmem, ix); |
1062 | 25.4M | if (G_UNLIKELY (thread_memory_magazine1_is_empty (tmem, ix))) |
1063 | 23.3M | thread_memory_magazine1_reload (tmem, ix); |
1064 | 25.4M | } |
1065 | 1.58G | mem = thread_memory_magazine1_alloc (tmem, ix); |
1066 | 1.58G | } |
1067 | 0 | else if (acat == 2) /* allocate through slab allocator */ |
1068 | 0 | { |
1069 | 0 | g_mutex_lock (&allocator->slab_mutex); |
1070 | 0 | mem = slab_allocator_alloc_chunk (chunk_size); |
1071 | 0 | g_mutex_unlock (&allocator->slab_mutex); |
1072 | 0 | } |
1073 | 0 | else /* delegate to system malloc */ |
1074 | 0 | mem = g_malloc (mem_size); |
1075 | 1.58G | if (G_UNLIKELY (allocator->config.debug_blocks)) |
1076 | 0 | smc_notify_alloc (mem, mem_size); |
1077 | | |
1078 | 1.58G | TRACE (GLIB_SLICE_ALLOC((void*)mem, mem_size)); |
1079 | | |
1080 | 1.58G | return mem; |
1081 | 1.58G | } |
1082 | | |
1083 | | /** |
1084 | | * g_slice_alloc0: |
1085 | | * @block_size: the number of bytes to allocate |
1086 | | * |
1087 | | * Allocates a block of memory via g_slice_alloc() and initializes |
1088 | | * the returned memory to 0. Note that the underlying slice allocation |
1089 | | * mechanism can be changed with the [`G_SLICE=always-malloc`][G_SLICE] |
1090 | | * environment variable. |
1091 | | * |
1092 | | * Returns: a pointer to the allocated block, which will be %NULL if and only |
1093 | | * if @mem_size is 0 |
1094 | | * |
1095 | | * Since: 2.10 |
1096 | | */ |
1097 | | gpointer |
1098 | | g_slice_alloc0 (gsize mem_size) |
1099 | 13.2k | { |
1100 | 13.2k | gpointer mem = g_slice_alloc (mem_size); |
1101 | 13.2k | if (mem) |
1102 | 13.2k | memset (mem, 0, mem_size); |
1103 | 13.2k | return mem; |
1104 | 13.2k | } |
1105 | | |
1106 | | /** |
1107 | | * g_slice_copy: |
1108 | | * @block_size: the number of bytes to allocate |
1109 | | * @mem_block: the memory to copy |
1110 | | * |
1111 | | * Allocates a block of memory from the slice allocator |
1112 | | * and copies @block_size bytes into it from @mem_block. |
1113 | | * |
1114 | | * @mem_block must be non-%NULL if @block_size is non-zero. |
1115 | | * |
1116 | | * Returns: a pointer to the allocated memory block, which will be %NULL if and |
1117 | | * only if @mem_size is 0 |
1118 | | * |
1119 | | * Since: 2.14 |
1120 | | */ |
1121 | | gpointer |
1122 | | g_slice_copy (gsize mem_size, |
1123 | | gconstpointer mem_block) |
1124 | 0 | { |
1125 | 0 | gpointer mem = g_slice_alloc (mem_size); |
1126 | 0 | if (mem) |
1127 | 0 | memcpy (mem, mem_block, mem_size); |
1128 | 0 | return mem; |
1129 | 0 | } |
1130 | | |
1131 | | /** |
1132 | | * g_slice_free1: |
1133 | | * @block_size: the size of the block |
1134 | | * @mem_block: a pointer to the block to free |
1135 | | * |
1136 | | * Frees a block of memory. |
1137 | | * |
1138 | | * The memory must have been allocated via g_slice_alloc() or |
1139 | | * g_slice_alloc0() and the @block_size has to match the size |
1140 | | * specified upon allocation. Note that the exact release behaviour |
1141 | | * can be changed with the [`G_DEBUG=gc-friendly`][G_DEBUG] environment |
1142 | | * variable, also see [`G_SLICE`][G_SLICE] for related debugging options. |
1143 | | * |
1144 | | * If @mem_block is %NULL, this function does nothing. |
1145 | | * |
1146 | | * Since: 2.10 |
1147 | | */ |
1148 | | void |
1149 | | g_slice_free1 (gsize mem_size, |
1150 | | gpointer mem_block) |
1151 | 119M | { |
1152 | 119M | gsize chunk_size = P2ALIGN (mem_size); |
1153 | 119M | guint acat = allocator_categorize (chunk_size); |
1154 | 119M | if (G_UNLIKELY (!mem_block)) |
1155 | 0 | return; |
1156 | 119M | if (G_UNLIKELY (allocator->config.debug_blocks) && |
1157 | 119M | !smc_notify_free (mem_block, mem_size)) |
1158 | 0 | abort(); |
1159 | 119M | if (G_LIKELY (acat == 1)) /* allocate through magazine layer */ |
1160 | 119M | { |
1161 | 119M | ThreadMemory *tmem = thread_memory_from_self(); |
1162 | 119M | guint ix = SLAB_INDEX (allocator, chunk_size); |
1163 | 119M | if (G_UNLIKELY (thread_memory_magazine2_is_full (tmem, ix))) |
1164 | 4.63M | { |
1165 | 4.63M | thread_memory_swap_magazines (tmem, ix); |
1166 | 4.63M | if (G_UNLIKELY (thread_memory_magazine2_is_full (tmem, ix))) |
1167 | 945k | thread_memory_magazine2_unload (tmem, ix); |
1168 | 4.63M | } |
1169 | 119M | if (G_UNLIKELY (g_mem_gc_friendly)) |
1170 | 0 | memset (mem_block, 0, chunk_size); |
1171 | 119M | thread_memory_magazine2_free (tmem, ix, mem_block); |
1172 | 119M | } |
1173 | 0 | else if (acat == 2) /* allocate through slab allocator */ |
1174 | 0 | { |
1175 | 0 | if (G_UNLIKELY (g_mem_gc_friendly)) |
1176 | 0 | memset (mem_block, 0, chunk_size); |
1177 | 0 | g_mutex_lock (&allocator->slab_mutex); |
1178 | 0 | slab_allocator_free_chunk (chunk_size, mem_block); |
1179 | 0 | g_mutex_unlock (&allocator->slab_mutex); |
1180 | 0 | } |
1181 | 0 | else /* delegate to system malloc */ |
1182 | 0 | { |
1183 | 0 | if (G_UNLIKELY (g_mem_gc_friendly)) |
1184 | 0 | memset (mem_block, 0, mem_size); |
1185 | 0 | g_free (mem_block); |
1186 | 0 | } |
1187 | 119M | TRACE (GLIB_SLICE_FREE((void*)mem_block, mem_size)); |
1188 | 119M | } |
1189 | | |
1190 | | /** |
1191 | | * g_slice_free_chain_with_offset: |
1192 | | * @block_size: the size of the blocks |
1193 | | * @mem_chain: a pointer to the first block of the chain |
1194 | | * @next_offset: the offset of the @next field in the blocks |
1195 | | * |
1196 | | * Frees a linked list of memory blocks of structure type @type. |
1197 | | * |
1198 | | * The memory blocks must be equal-sized, allocated via |
1199 | | * g_slice_alloc() or g_slice_alloc0() and linked together by a |
1200 | | * @next pointer (similar to #GSList). The offset of the @next |
1201 | | * field in each block is passed as third argument. |
1202 | | * Note that the exact release behaviour can be changed with the |
1203 | | * [`G_DEBUG=gc-friendly`][G_DEBUG] environment variable, also see |
1204 | | * [`G_SLICE`][G_SLICE] for related debugging options. |
1205 | | * |
1206 | | * If @mem_chain is %NULL, this function does nothing. |
1207 | | * |
1208 | | * Since: 2.10 |
1209 | | */ |
1210 | | void |
1211 | | g_slice_free_chain_with_offset (gsize mem_size, |
1212 | | gpointer mem_chain, |
1213 | | gsize next_offset) |
1214 | 338M | { |
1215 | 338M | gpointer slice = mem_chain; |
1216 | | /* while the thread magazines and the magazine cache are implemented so that |
1217 | | * they can easily be extended to allow for free lists containing more free |
1218 | | * lists for the first level nodes, which would allow O(1) freeing in this |
1219 | | * function, the benefit of such an extension is questionable, because: |
1220 | | * - the magazine size counts will become mere lower bounds which confuses |
1221 | | * the code adapting to lock contention; |
1222 | | * - freeing a single node to the thread magazines is very fast, so this |
1223 | | * O(list_length) operation is multiplied by a fairly small factor; |
1224 | | * - memory usage histograms on larger applications seem to indicate that |
1225 | | * the amount of released multi node lists is negligible in comparison |
1226 | | * to single node releases. |
1227 | | * - the major performance bottle neck, namely g_private_get() or |
1228 | | * g_mutex_lock()/g_mutex_unlock() has already been moved out of the |
1229 | | * inner loop for freeing chained slices. |
1230 | | */ |
1231 | 338M | gsize chunk_size = P2ALIGN (mem_size); |
1232 | 338M | guint acat = allocator_categorize (chunk_size); |
1233 | 338M | if (G_LIKELY (acat == 1)) /* allocate through magazine layer */ |
1234 | 338M | { |
1235 | 338M | ThreadMemory *tmem = thread_memory_from_self(); |
1236 | 338M | guint ix = SLAB_INDEX (allocator, chunk_size); |
1237 | 1.80G | while (slice) |
1238 | 1.46G | { |
1239 | 1.46G | guint8 *current = slice; |
1240 | 1.46G | slice = *(gpointer*) (current + next_offset); |
1241 | 1.46G | if (G_UNLIKELY (allocator->config.debug_blocks) && |
1242 | 1.46G | !smc_notify_free (current, mem_size)) |
1243 | 0 | abort(); |
1244 | 1.46G | if (G_UNLIKELY (thread_memory_magazine2_is_full (tmem, ix))) |
1245 | 43.6M | { |
1246 | 43.6M | thread_memory_swap_magazines (tmem, ix); |
1247 | 43.6M | if (G_UNLIKELY (thread_memory_magazine2_is_full (tmem, ix))) |
1248 | 22.4M | thread_memory_magazine2_unload (tmem, ix); |
1249 | 43.6M | } |
1250 | 1.46G | if (G_UNLIKELY (g_mem_gc_friendly)) |
1251 | 0 | memset (current, 0, chunk_size); |
1252 | 1.46G | thread_memory_magazine2_free (tmem, ix, current); |
1253 | 1.46G | } |
1254 | 338M | } |
1255 | 0 | else if (acat == 2) /* allocate through slab allocator */ |
1256 | 0 | { |
1257 | 0 | g_mutex_lock (&allocator->slab_mutex); |
1258 | 0 | while (slice) |
1259 | 0 | { |
1260 | 0 | guint8 *current = slice; |
1261 | 0 | slice = *(gpointer*) (current + next_offset); |
1262 | 0 | if (G_UNLIKELY (allocator->config.debug_blocks) && |
1263 | 0 | !smc_notify_free (current, mem_size)) |
1264 | 0 | abort(); |
1265 | 0 | if (G_UNLIKELY (g_mem_gc_friendly)) |
1266 | 0 | memset (current, 0, chunk_size); |
1267 | 0 | slab_allocator_free_chunk (chunk_size, current); |
1268 | 0 | } |
1269 | 0 | g_mutex_unlock (&allocator->slab_mutex); |
1270 | 0 | } |
1271 | 0 | else /* delegate to system malloc */ |
1272 | 0 | while (slice) |
1273 | 0 | { |
1274 | 0 | guint8 *current = slice; |
1275 | 0 | slice = *(gpointer*) (current + next_offset); |
1276 | 0 | if (G_UNLIKELY (allocator->config.debug_blocks) && |
1277 | 0 | !smc_notify_free (current, mem_size)) |
1278 | 0 | abort(); |
1279 | 0 | if (G_UNLIKELY (g_mem_gc_friendly)) |
1280 | 0 | memset (current, 0, mem_size); |
1281 | 0 | g_free (current); |
1282 | 0 | } |
1283 | 338M | } |
1284 | | |
1285 | | /* --- single page allocator --- */ |
1286 | | static void |
1287 | | allocator_slab_stack_push (Allocator *local_allocator, |
1288 | | guint ix, |
1289 | | SlabInfo *sinfo) |
1290 | 36.4k | { |
1291 | | /* insert slab at slab ring head */ |
1292 | 36.4k | if (!local_allocator->slab_stack[ix]) |
1293 | 32 | { |
1294 | 32 | sinfo->next = sinfo; |
1295 | 32 | sinfo->prev = sinfo; |
1296 | 32 | } |
1297 | 36.3k | else |
1298 | 36.3k | { |
1299 | 36.3k | SlabInfo *next = local_allocator->slab_stack[ix], *prev = next->prev; |
1300 | 36.3k | next->prev = sinfo; |
1301 | 36.3k | prev->next = sinfo; |
1302 | 36.3k | sinfo->next = next; |
1303 | 36.3k | sinfo->prev = prev; |
1304 | 36.3k | } |
1305 | 36.4k | local_allocator->slab_stack[ix] = sinfo; |
1306 | 36.4k | } |
1307 | | |
1308 | | static gsize |
1309 | | allocator_aligned_page_size (Allocator *local_allocator, |
1310 | | gsize n_bytes) |
1311 | 111k | { |
1312 | 111k | gsize val = (gsize) 1 << g_bit_storage (n_bytes - 1); |
1313 | 111k | val = MAX (val, local_allocator->min_page_size); |
1314 | 111k | return val; |
1315 | 111k | } |
1316 | | |
1317 | | static void |
1318 | | allocator_add_slab (Allocator *local_allocator, |
1319 | | guint ix, |
1320 | | gsize chunk_size) |
1321 | 23.3k | { |
1322 | 23.3k | ChunkLink *chunk; |
1323 | 23.3k | SlabInfo *sinfo; |
1324 | 23.3k | gsize addr, padding, n_chunks, color = 0; |
1325 | 23.3k | gsize page_size; |
1326 | 23.3k | int errsv; |
1327 | 23.3k | gpointer aligned_memory; |
1328 | 23.3k | guint8 *mem; |
1329 | 23.3k | guint i; |
1330 | | |
1331 | 23.3k | page_size = allocator_aligned_page_size (local_allocator, SLAB_BPAGE_SIZE (local_allocator, chunk_size)); |
1332 | | /* allocate 1 page for the chunks and the slab */ |
1333 | 23.3k | aligned_memory = allocator_memalign (page_size, page_size - NATIVE_MALLOC_PADDING); |
1334 | 23.3k | errsv = errno; |
1335 | 23.3k | mem = aligned_memory; |
1336 | | |
1337 | 23.3k | if (!mem) |
1338 | 0 | { |
1339 | 0 | const gchar *syserr = strerror (errsv); |
1340 | 0 | mem_error ("failed to allocate %u bytes (alignment: %u): %s\n", |
1341 | 0 | (guint) (page_size - NATIVE_MALLOC_PADDING), (guint) page_size, syserr); |
1342 | 0 | } |
1343 | | /* mask page address */ |
1344 | 23.3k | addr = ((gsize) mem / page_size) * page_size; |
1345 | | /* assert alignment */ |
1346 | 23.3k | mem_assert (aligned_memory == (gpointer) addr); |
1347 | | /* basic slab info setup */ |
1348 | 23.3k | sinfo = (SlabInfo*) (mem + page_size - SLAB_INFO_SIZE); |
1349 | 23.3k | sinfo->n_allocated = 0; |
1350 | 23.3k | sinfo->chunks = NULL; |
1351 | | /* figure cache colorization */ |
1352 | 23.3k | n_chunks = ((guint8*) sinfo - mem) / chunk_size; |
1353 | 23.3k | padding = ((guint8*) sinfo - mem) - n_chunks * chunk_size; |
1354 | 23.3k | if (padding) |
1355 | 12.4k | { |
1356 | 12.4k | color = (local_allocator->color_accu * P2ALIGNMENT) % padding; |
1357 | 12.4k | local_allocator->color_accu += local_allocator->config.color_increment; |
1358 | 12.4k | } |
1359 | | /* add chunks to free list */ |
1360 | 23.3k | chunk = (ChunkLink*) (mem + color); |
1361 | 23.3k | sinfo->chunks = chunk; |
1362 | 273k | for (i = 0; i < n_chunks - 1; i++) |
1363 | 250k | { |
1364 | 250k | chunk->next = (ChunkLink*) ((guint8*) chunk + chunk_size); |
1365 | 250k | chunk = chunk->next; |
1366 | 250k | } |
1367 | 23.3k | chunk->next = NULL; /* last chunk */ |
1368 | | /* add slab to slab ring */ |
1369 | 23.3k | allocator_slab_stack_push (local_allocator, ix, sinfo); |
1370 | 23.3k | } |
1371 | | |
1372 | | static gpointer |
1373 | | slab_allocator_alloc_chunk (gsize chunk_size) |
1374 | 312k | { |
1375 | 312k | ChunkLink *chunk; |
1376 | 312k | guint ix = SLAB_INDEX (allocator, chunk_size); |
1377 | | /* ensure non-empty slab */ |
1378 | 312k | if (!allocator->slab_stack[ix] || !allocator->slab_stack[ix]->chunks) |
1379 | 23.3k | allocator_add_slab (allocator, ix, chunk_size); |
1380 | | /* allocate chunk */ |
1381 | 312k | chunk = allocator->slab_stack[ix]->chunks; |
1382 | 312k | allocator->slab_stack[ix]->chunks = chunk->next; |
1383 | 312k | allocator->slab_stack[ix]->n_allocated++; |
1384 | | /* rotate empty slabs */ |
1385 | 312k | if (!allocator->slab_stack[ix]->chunks) |
1386 | 32.5k | allocator->slab_stack[ix] = allocator->slab_stack[ix]->next; |
1387 | 312k | return chunk; |
1388 | 312k | } |
1389 | | |
1390 | | static void |
1391 | | slab_allocator_free_chunk (gsize chunk_size, |
1392 | | gpointer mem) |
1393 | 88.3k | { |
1394 | 88.3k | ChunkLink *chunk; |
1395 | 88.3k | gboolean was_empty; |
1396 | 88.3k | guint ix = SLAB_INDEX (allocator, chunk_size); |
1397 | 88.3k | gsize page_size = allocator_aligned_page_size (allocator, SLAB_BPAGE_SIZE (allocator, chunk_size)); |
1398 | 88.3k | gsize addr = ((gsize) mem / page_size) * page_size; |
1399 | | /* mask page address */ |
1400 | 88.3k | guint8 *page = (guint8*) addr; |
1401 | 88.3k | SlabInfo *sinfo = (SlabInfo*) (page + page_size - SLAB_INFO_SIZE); |
1402 | | /* assert valid chunk count */ |
1403 | 88.3k | mem_assert (sinfo->n_allocated > 0); |
1404 | | /* add chunk to free list */ |
1405 | 88.3k | was_empty = sinfo->chunks == NULL; |
1406 | 88.3k | chunk = (ChunkLink*) mem; |
1407 | 88.3k | chunk->next = sinfo->chunks; |
1408 | 88.3k | sinfo->chunks = chunk; |
1409 | 88.3k | sinfo->n_allocated--; |
1410 | | /* keep slab ring partially sorted, empty slabs at end */ |
1411 | 88.3k | if (was_empty) |
1412 | 13.0k | { |
1413 | | /* unlink slab */ |
1414 | 13.0k | SlabInfo *next = sinfo->next, *prev = sinfo->prev; |
1415 | 13.0k | next->prev = prev; |
1416 | 13.0k | prev->next = next; |
1417 | 13.0k | if (allocator->slab_stack[ix] == sinfo) |
1418 | 0 | allocator->slab_stack[ix] = next == sinfo ? NULL : next; |
1419 | | /* insert slab at head */ |
1420 | 13.0k | allocator_slab_stack_push (allocator, ix, sinfo); |
1421 | 13.0k | } |
1422 | | /* eagerly free complete unused slabs */ |
1423 | 88.3k | if (!sinfo->n_allocated) |
1424 | 3.81k | { |
1425 | | /* unlink slab */ |
1426 | 3.81k | SlabInfo *next = sinfo->next, *prev = sinfo->prev; |
1427 | 3.81k | next->prev = prev; |
1428 | 3.81k | prev->next = next; |
1429 | 3.81k | if (allocator->slab_stack[ix] == sinfo) |
1430 | 413 | allocator->slab_stack[ix] = next == sinfo ? NULL : next; |
1431 | | /* free slab */ |
1432 | 3.81k | allocator_memfree (page_size, page); |
1433 | 3.81k | } |
1434 | 88.3k | } |
1435 | | |
1436 | | /* --- memalign implementation --- */ |
1437 | | #ifdef HAVE_MALLOC_H |
1438 | | #include <malloc.h> /* memalign() */ |
1439 | | #endif |
1440 | | |
1441 | | /* from config.h: |
1442 | | * define HAVE_POSIX_MEMALIGN 1 // if free(posix_memalign(3)) works, <stdlib.h> |
1443 | | * define HAVE_MEMALIGN 1 // if free(memalign(3)) works, <malloc.h> |
1444 | | * define HAVE_VALLOC 1 // if free(valloc(3)) works, <stdlib.h> or <malloc.h> |
1445 | | * if none is provided, we implement malloc(3)-based alloc-only page alignment |
1446 | | */ |
1447 | | |
1448 | | #if !(HAVE_POSIX_MEMALIGN || HAVE_MEMALIGN || HAVE_VALLOC) |
1449 | | G_GNUC_BEGIN_IGNORE_DEPRECATIONS |
1450 | | static GTrashStack *compat_valloc_trash = NULL; |
1451 | | G_GNUC_END_IGNORE_DEPRECATIONS |
1452 | | #endif |
1453 | | |
1454 | | static gpointer |
1455 | | allocator_memalign (gsize alignment, |
1456 | | gsize memsize) |
1457 | 23.3k | { |
1458 | 23.3k | gpointer aligned_memory = NULL; |
1459 | 23.3k | gint err = ENOMEM; |
1460 | 23.3k | #if HAVE_POSIX_MEMALIGN |
1461 | 23.3k | err = posix_memalign (&aligned_memory, alignment, memsize); |
1462 | | #elif HAVE_MEMALIGN |
1463 | | errno = 0; |
1464 | | aligned_memory = memalign (alignment, memsize); |
1465 | | err = errno; |
1466 | | #elif HAVE_VALLOC |
1467 | | errno = 0; |
1468 | | aligned_memory = valloc (memsize); |
1469 | | err = errno; |
1470 | | #else |
1471 | | /* simplistic non-freeing page allocator */ |
1472 | | mem_assert (alignment == sys_page_size); |
1473 | | mem_assert (memsize <= sys_page_size); |
1474 | | if (!compat_valloc_trash) |
1475 | | { |
1476 | | const guint n_pages = 16; |
1477 | | guint8 *mem = malloc (n_pages * sys_page_size); |
1478 | | err = errno; |
1479 | | if (mem) |
1480 | | { |
1481 | | gint i = n_pages; |
1482 | | guint8 *amem = (guint8*) ALIGN ((gsize) mem, sys_page_size); |
1483 | | if (amem != mem) |
1484 | | i--; /* mem wasn't page aligned */ |
1485 | | G_GNUC_BEGIN_IGNORE_DEPRECATIONS |
1486 | | while (--i >= 0) |
1487 | | g_trash_stack_push (&compat_valloc_trash, amem + i * sys_page_size); |
1488 | | G_GNUC_END_IGNORE_DEPRECATIONS |
1489 | | } |
1490 | | } |
1491 | | G_GNUC_BEGIN_IGNORE_DEPRECATIONS |
1492 | | aligned_memory = g_trash_stack_pop (&compat_valloc_trash); |
1493 | | G_GNUC_END_IGNORE_DEPRECATIONS |
1494 | | #endif |
1495 | 23.3k | if (!aligned_memory) |
1496 | 0 | errno = err; |
1497 | 23.3k | return aligned_memory; |
1498 | 23.3k | } |
1499 | | |
1500 | | static void |
1501 | | allocator_memfree (gsize memsize, |
1502 | | gpointer mem) |
1503 | 3.81k | { |
1504 | 3.81k | #if HAVE_POSIX_MEMALIGN || HAVE_MEMALIGN || HAVE_VALLOC |
1505 | 3.81k | free (mem); |
1506 | | #else |
1507 | | mem_assert (memsize <= sys_page_size); |
1508 | | G_GNUC_BEGIN_IGNORE_DEPRECATIONS |
1509 | | g_trash_stack_push (&compat_valloc_trash, mem); |
1510 | | G_GNUC_END_IGNORE_DEPRECATIONS |
1511 | | #endif |
1512 | 3.81k | } |
1513 | | |
1514 | | static void |
1515 | | mem_error (const char *format, |
1516 | | ...) |
1517 | 0 | { |
1518 | 0 | const char *pname; |
1519 | 0 | va_list args; |
1520 | | /* at least, put out "MEMORY-ERROR", in case we segfault during the rest of the function */ |
1521 | 0 | fputs ("\n***MEMORY-ERROR***: ", stderr); |
1522 | 0 | pname = g_get_prgname(); |
1523 | 0 | g_fprintf (stderr, "%s[%ld]: GSlice: ", pname ? pname : "", (long)getpid()); |
1524 | 0 | va_start (args, format); |
1525 | 0 | g_vfprintf (stderr, format, args); |
1526 | 0 | va_end (args); |
1527 | 0 | fputs ("\n", stderr); |
1528 | 0 | abort(); |
1529 | 0 | _exit (1); |
1530 | 0 | } |
1531 | | |
1532 | | /* --- g-slice memory checker tree --- */ |
1533 | | typedef size_t SmcKType; /* key type */ |
1534 | | typedef size_t SmcVType; /* value type */ |
1535 | | typedef struct { |
1536 | | SmcKType key; |
1537 | | SmcVType value; |
1538 | | } SmcEntry; |
1539 | | static void smc_tree_insert (SmcKType key, |
1540 | | SmcVType value); |
1541 | | static gboolean smc_tree_lookup (SmcKType key, |
1542 | | SmcVType *value_p); |
1543 | | static gboolean smc_tree_remove (SmcKType key); |
1544 | | |
1545 | | |
1546 | | /* --- g-slice memory checker implementation --- */ |
1547 | | static void |
1548 | | smc_notify_alloc (void *pointer, |
1549 | | size_t size) |
1550 | 0 | { |
1551 | 0 | size_t address = (size_t) pointer; |
1552 | 0 | if (pointer) |
1553 | 0 | smc_tree_insert (address, size); |
1554 | 0 | } |
1555 | | |
1556 | | #if 0 |
1557 | | static void |
1558 | | smc_notify_ignore (void *pointer) |
1559 | | { |
1560 | | size_t address = (size_t) pointer; |
1561 | | if (pointer) |
1562 | | smc_tree_remove (address); |
1563 | | } |
1564 | | #endif |
1565 | | |
1566 | | static int |
1567 | | smc_notify_free (void *pointer, |
1568 | | size_t size) |
1569 | 0 | { |
1570 | 0 | size_t address = (size_t) pointer; |
1571 | 0 | SmcVType real_size; |
1572 | 0 | gboolean found_one; |
1573 | |
|
1574 | 0 | if (!pointer) |
1575 | 0 | return 1; /* ignore */ |
1576 | 0 | found_one = smc_tree_lookup (address, &real_size); |
1577 | 0 | if (!found_one) |
1578 | 0 | { |
1579 | 0 | g_fprintf (stderr, "GSlice: MemChecker: attempt to release non-allocated block: %p size=%" G_GSIZE_FORMAT "\n", pointer, size); |
1580 | 0 | return 0; |
1581 | 0 | } |
1582 | 0 | if (real_size != size && (real_size || size)) |
1583 | 0 | { |
1584 | 0 | g_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); |
1585 | 0 | return 0; |
1586 | 0 | } |
1587 | 0 | if (!smc_tree_remove (address)) |
1588 | 0 | { |
1589 | 0 | g_fprintf (stderr, "GSlice: MemChecker: attempt to release non-allocated block: %p size=%" G_GSIZE_FORMAT "\n", pointer, size); |
1590 | 0 | return 0; |
1591 | 0 | } |
1592 | 0 | return 1; /* all fine */ |
1593 | 0 | } |
1594 | | |
1595 | | /* --- g-slice memory checker tree implementation --- */ |
1596 | 0 | #define SMC_TRUNK_COUNT (4093 /* 16381 */) /* prime, to distribute trunk collisions (big, allocated just once) */ |
1597 | 0 | #define SMC_BRANCH_COUNT (511) /* prime, to distribute branch collisions */ |
1598 | 0 | #define SMC_TRUNK_EXTENT (SMC_BRANCH_COUNT * 2039) /* key address space per trunk, should distribute uniformly across BRANCH_COUNT */ |
1599 | 0 | #define SMC_TRUNK_HASH(k) ((k / SMC_TRUNK_EXTENT) % SMC_TRUNK_COUNT) /* generate new trunk hash per megabyte (roughly) */ |
1600 | 0 | #define SMC_BRANCH_HASH(k) (k % SMC_BRANCH_COUNT) |
1601 | | |
1602 | | typedef struct { |
1603 | | SmcEntry *entries; |
1604 | | unsigned int n_entries; |
1605 | | } SmcBranch; |
1606 | | |
1607 | | static SmcBranch **smc_tree_root = NULL; |
1608 | | |
1609 | | static void |
1610 | | smc_tree_abort (int errval) |
1611 | 0 | { |
1612 | 0 | const char *syserr = strerror (errval); |
1613 | 0 | mem_error ("MemChecker: failure in debugging tree: %s", syserr); |
1614 | 0 | } |
1615 | | |
1616 | | static inline SmcEntry* |
1617 | | smc_tree_branch_grow_L (SmcBranch *branch, |
1618 | | unsigned int index) |
1619 | 0 | { |
1620 | 0 | unsigned int old_size = branch->n_entries * sizeof (branch->entries[0]); |
1621 | 0 | unsigned int new_size = old_size + sizeof (branch->entries[0]); |
1622 | 0 | SmcEntry *entry; |
1623 | 0 | mem_assert (index <= branch->n_entries); |
1624 | 0 | branch->entries = (SmcEntry*) realloc (branch->entries, new_size); |
1625 | 0 | if (!branch->entries) |
1626 | 0 | smc_tree_abort (errno); |
1627 | 0 | entry = branch->entries + index; |
1628 | 0 | memmove (entry + 1, entry, (branch->n_entries - index) * sizeof (entry[0])); |
1629 | 0 | branch->n_entries += 1; |
1630 | 0 | return entry; |
1631 | 0 | } |
1632 | | |
1633 | | static inline SmcEntry* |
1634 | | smc_tree_branch_lookup_nearest_L (SmcBranch *branch, |
1635 | | SmcKType key) |
1636 | 0 | { |
1637 | 0 | unsigned int n_nodes = branch->n_entries, offs = 0; |
1638 | 0 | SmcEntry *check = branch->entries; |
1639 | 0 | int cmp = 0; |
1640 | 0 | while (offs < n_nodes) |
1641 | 0 | { |
1642 | 0 | unsigned int i = (offs + n_nodes) >> 1; |
1643 | 0 | check = branch->entries + i; |
1644 | 0 | cmp = key < check->key ? -1 : key != check->key; |
1645 | 0 | if (cmp == 0) |
1646 | 0 | return check; /* return exact match */ |
1647 | 0 | else if (cmp < 0) |
1648 | 0 | n_nodes = i; |
1649 | 0 | else /* (cmp > 0) */ |
1650 | 0 | offs = i + 1; |
1651 | 0 | } |
1652 | | /* check points at last mismatch, cmp > 0 indicates greater key */ |
1653 | 0 | return cmp > 0 ? check + 1 : check; /* return insertion position for inexact match */ |
1654 | 0 | } |
1655 | | |
1656 | | static void |
1657 | | smc_tree_insert (SmcKType key, |
1658 | | SmcVType value) |
1659 | 0 | { |
1660 | 0 | unsigned int ix0, ix1; |
1661 | 0 | SmcEntry *entry; |
1662 | |
|
1663 | 0 | g_mutex_lock (&smc_tree_mutex); |
1664 | 0 | ix0 = SMC_TRUNK_HASH (key); |
1665 | 0 | ix1 = SMC_BRANCH_HASH (key); |
1666 | 0 | if (!smc_tree_root) |
1667 | 0 | { |
1668 | 0 | smc_tree_root = calloc (SMC_TRUNK_COUNT, sizeof (smc_tree_root[0])); |
1669 | 0 | if (!smc_tree_root) |
1670 | 0 | smc_tree_abort (errno); |
1671 | 0 | } |
1672 | 0 | if (!smc_tree_root[ix0]) |
1673 | 0 | { |
1674 | 0 | smc_tree_root[ix0] = calloc (SMC_BRANCH_COUNT, sizeof (smc_tree_root[0][0])); |
1675 | 0 | if (!smc_tree_root[ix0]) |
1676 | 0 | smc_tree_abort (errno); |
1677 | 0 | } |
1678 | 0 | entry = smc_tree_branch_lookup_nearest_L (&smc_tree_root[ix0][ix1], key); |
1679 | 0 | if (!entry || /* need create */ |
1680 | 0 | entry >= smc_tree_root[ix0][ix1].entries + smc_tree_root[ix0][ix1].n_entries || /* need append */ |
1681 | 0 | entry->key != key) /* need insert */ |
1682 | 0 | entry = smc_tree_branch_grow_L (&smc_tree_root[ix0][ix1], entry - smc_tree_root[ix0][ix1].entries); |
1683 | 0 | entry->key = key; |
1684 | 0 | entry->value = value; |
1685 | 0 | g_mutex_unlock (&smc_tree_mutex); |
1686 | 0 | } |
1687 | | |
1688 | | static gboolean |
1689 | | smc_tree_lookup (SmcKType key, |
1690 | | SmcVType *value_p) |
1691 | 0 | { |
1692 | 0 | SmcEntry *entry = NULL; |
1693 | 0 | unsigned int ix0 = SMC_TRUNK_HASH (key), ix1 = SMC_BRANCH_HASH (key); |
1694 | 0 | gboolean found_one = FALSE; |
1695 | 0 | *value_p = 0; |
1696 | 0 | g_mutex_lock (&smc_tree_mutex); |
1697 | 0 | if (smc_tree_root && smc_tree_root[ix0]) |
1698 | 0 | { |
1699 | 0 | entry = smc_tree_branch_lookup_nearest_L (&smc_tree_root[ix0][ix1], key); |
1700 | 0 | if (entry && |
1701 | 0 | entry < smc_tree_root[ix0][ix1].entries + smc_tree_root[ix0][ix1].n_entries && |
1702 | 0 | entry->key == key) |
1703 | 0 | { |
1704 | 0 | found_one = TRUE; |
1705 | 0 | *value_p = entry->value; |
1706 | 0 | } |
1707 | 0 | } |
1708 | 0 | g_mutex_unlock (&smc_tree_mutex); |
1709 | 0 | return found_one; |
1710 | 0 | } |
1711 | | |
1712 | | static gboolean |
1713 | | smc_tree_remove (SmcKType key) |
1714 | 0 | { |
1715 | 0 | unsigned int ix0 = SMC_TRUNK_HASH (key), ix1 = SMC_BRANCH_HASH (key); |
1716 | 0 | gboolean found_one = FALSE; |
1717 | 0 | g_mutex_lock (&smc_tree_mutex); |
1718 | 0 | if (smc_tree_root && smc_tree_root[ix0]) |
1719 | 0 | { |
1720 | 0 | SmcEntry *entry = smc_tree_branch_lookup_nearest_L (&smc_tree_root[ix0][ix1], key); |
1721 | 0 | if (entry && |
1722 | 0 | entry < smc_tree_root[ix0][ix1].entries + smc_tree_root[ix0][ix1].n_entries && |
1723 | 0 | entry->key == key) |
1724 | 0 | { |
1725 | 0 | unsigned int i = entry - smc_tree_root[ix0][ix1].entries; |
1726 | 0 | smc_tree_root[ix0][ix1].n_entries -= 1; |
1727 | 0 | memmove (entry, entry + 1, (smc_tree_root[ix0][ix1].n_entries - i) * sizeof (entry[0])); |
1728 | 0 | if (!smc_tree_root[ix0][ix1].n_entries) |
1729 | 0 | { |
1730 | | /* avoid useless pressure on the memory system */ |
1731 | 0 | free (smc_tree_root[ix0][ix1].entries); |
1732 | 0 | smc_tree_root[ix0][ix1].entries = NULL; |
1733 | 0 | } |
1734 | 0 | found_one = TRUE; |
1735 | 0 | } |
1736 | 0 | } |
1737 | 0 | g_mutex_unlock (&smc_tree_mutex); |
1738 | 0 | return found_one; |
1739 | 0 | } |
1740 | | |
1741 | | #ifdef G_ENABLE_DEBUG |
1742 | | void |
1743 | | g_slice_debug_tree_statistics (void) |
1744 | 0 | { |
1745 | 0 | g_mutex_lock (&smc_tree_mutex); |
1746 | 0 | if (smc_tree_root) |
1747 | 0 | { |
1748 | 0 | unsigned int i, j, t = 0, o = 0, b = 0, su = 0, ex = 0, en = 4294967295u; |
1749 | 0 | double tf, bf; |
1750 | 0 | for (i = 0; i < SMC_TRUNK_COUNT; i++) |
1751 | 0 | if (smc_tree_root[i]) |
1752 | 0 | { |
1753 | 0 | t++; |
1754 | 0 | for (j = 0; j < SMC_BRANCH_COUNT; j++) |
1755 | 0 | if (smc_tree_root[i][j].n_entries) |
1756 | 0 | { |
1757 | 0 | b++; |
1758 | 0 | su += smc_tree_root[i][j].n_entries; |
1759 | 0 | en = MIN (en, smc_tree_root[i][j].n_entries); |
1760 | 0 | ex = MAX (ex, smc_tree_root[i][j].n_entries); |
1761 | 0 | } |
1762 | 0 | else if (smc_tree_root[i][j].entries) |
1763 | 0 | o++; /* formerly used, now empty */ |
1764 | 0 | } |
1765 | 0 | en = b ? en : 0; |
1766 | 0 | tf = MAX (t, 1.0); /* max(1) to be a valid divisor */ |
1767 | 0 | bf = MAX (b, 1.0); /* max(1) to be a valid divisor */ |
1768 | 0 | g_fprintf (stderr, "GSlice: MemChecker: %u trunks, %u branches, %u old branches\n", t, b, o); |
1769 | 0 | g_fprintf (stderr, "GSlice: MemChecker: %f branches per trunk, %.2f%% utilization\n", |
1770 | 0 | b / tf, |
1771 | 0 | 100.0 - (SMC_BRANCH_COUNT - b / tf) / (0.01 * SMC_BRANCH_COUNT)); |
1772 | 0 | g_fprintf (stderr, "GSlice: MemChecker: %f entries per branch, %u minimum, %u maximum\n", |
1773 | 0 | su / bf, en, ex); |
1774 | 0 | } |
1775 | 0 | else |
1776 | 0 | g_fprintf (stderr, "GSlice: MemChecker: root=NULL\n"); |
1777 | 0 | g_mutex_unlock (&smc_tree_mutex); |
1778 | | |
1779 | | /* sample statistics (beast + GSLice + 24h scripted core & GUI activity): |
1780 | | * PID %CPU %MEM VSZ RSS COMMAND |
1781 | | * 8887 30.3 45.8 456068 414856 beast-0.7.1 empty.bse |
1782 | | * $ cat /proc/8887/statm # total-program-size resident-set-size shared-pages text/code data/stack library dirty-pages |
1783 | | * 114017 103714 2354 344 0 108676 0 |
1784 | | * $ cat /proc/8887/status |
1785 | | * Name: beast-0.7.1 |
1786 | | * VmSize: 456068 kB |
1787 | | * VmLck: 0 kB |
1788 | | * VmRSS: 414856 kB |
1789 | | * VmData: 434620 kB |
1790 | | * VmStk: 84 kB |
1791 | | * VmExe: 1376 kB |
1792 | | * VmLib: 13036 kB |
1793 | | * VmPTE: 456 kB |
1794 | | * Threads: 3 |
1795 | | * (gdb) print g_slice_debug_tree_statistics () |
1796 | | * GSlice: MemChecker: 422 trunks, 213068 branches, 0 old branches |
1797 | | * GSlice: MemChecker: 504.900474 branches per trunk, 98.81% utilization |
1798 | | * GSlice: MemChecker: 4.965039 entries per branch, 1 minimum, 37 maximum |
1799 | | */ |
1800 | 0 | } |
1801 | | #endif /* G_ENABLE_DEBUG */ |