3 * efficient memory allocation
5 * MonoMemPool is for fast allocation of memory. We free
6 * all memory when the pool is destroyed.
9 * Dietmar Maurer (dietmar@ximian.com)
11 * Copyright 2001-2003 Ximian, Inc (http://www.ximian.com)
12 * Copyright 2004-2009 Novell, Inc (http://www.novell.com)
13 * Copyright 2011 Xamarin Inc. (http://www.xamarin.com)
14 * Licensed under the MIT license. See LICENSE file in the project root for full license information.
22 #include "mempool-internals.h"
23 #include "utils/mono-compiler.h"
26 * MonoMemPool is for fast allocation of memory. We free
27 * all memory when the pool is destroyed.
31 #define ALIGN_SIZE(s) (((s) + MEM_ALIGN - 1) & ~(MEM_ALIGN - 1))
33 // Size of memory at start of mempool reserved for header
34 #define SIZEOF_MEM_POOL (ALIGN_SIZE (sizeof (MonoMemPool)))
37 #define MONO_MEMPOOL_PAGESIZE 4096
38 #define MONO_MEMPOOL_MINSIZE 256
40 #define MONO_MEMPOOL_PAGESIZE 8192
41 #define MONO_MEMPOOL_MINSIZE 512
44 // The --with-malloc-mempools debug-build flag causes mempools to be allocated in single-element blocks, so tools like Valgrind can run better.
45 #if USE_MALLOC_FOR_MEMPOOLS
46 #define INDIVIDUAL_ALLOCATIONS
47 #define MONO_MEMPOOL_PREFER_INDIVIDUAL_ALLOCATION_SIZE 0
49 #define MONO_MEMPOOL_PREFER_INDIVIDUAL_ALLOCATION_SIZE MONO_MEMPOOL_PAGESIZE
53 #define G_LIKELY(a) (a)
54 #define G_UNLIKELY(a) (a)
57 // A mempool is a linked list of memory blocks, each of which begins with this header structure.
58 // The initial block in the linked list is special, and tracks additional information.
60 // Next block after this one in linked list
63 // Size of this memory block only
66 // Used in "initial block" only: Beginning of current free space in mempool (may be in some block other than the first one)
69 // Used in "initial block" only: End of current free space in mempool (ie, the first byte following the end of usable space)
73 // Unused: Imposing floating point memory rules on _MonoMemPool's final field ensures proper alignment of whole header struct
76 // Used in "initial block" only: Number of bytes so far allocated (whether used or not) in the whole mempool
81 static long total_bytes_allocated = 0;
86 * Returns: a new memory pool.
89 mono_mempool_new (void)
91 return mono_mempool_new_size (MONO_MEMPOOL_PAGESIZE);
95 * mono_mempool_new_size:
97 * clang's ThreadSanitizer detects races of total_bytes_allocated and pool->d.allocated throughout the functions
98 * * mono_mempool_alloc
99 * * mono_mempool_new_size
100 * * mono_mempool_destroy
101 * while these races could lead to wrong values, total_bytes_allocated is just used for debugging / reporting and since
102 * the mempool.c functions are called quite often, a discussion led the the conclusion of ignoring these races:
103 * https://bugzilla.xamarin.com/show_bug.cgi?id=57936
105 * \param initial_size the amount of memory to initially reserve for the memory pool.
106 * \returns a new memory pool with a specific initial memory reservation.
108 MONO_NO_SANITIZE_THREAD
110 mono_mempool_new_size (int initial_size)
114 #ifdef INDIVIDUAL_ALLOCATIONS
115 // In individual allocation mode, create initial block with zero storage space.
116 initial_size = SIZEOF_MEM_POOL;
118 if (initial_size < MONO_MEMPOOL_MINSIZE)
119 initial_size = MONO_MEMPOOL_MINSIZE;
122 pool = (MonoMemPool *)g_malloc (initial_size);
125 pool->pos = (guint8*)pool + SIZEOF_MEM_POOL; // Start after header
126 pool->end = (guint8*)pool + initial_size; // End at end of allocated space
127 pool->d.allocated = pool->size = initial_size;
128 total_bytes_allocated += initial_size;
133 * mono_mempool_destroy:
135 * clang's ThreadSanitizer detects races of total_bytes_allocated and pool->d.allocated throughout the functions
136 * * mono_mempool_alloc
137 * * mono_mempool_new_size
138 * * mono_mempool_destroy
139 * while these races could lead to wrong values, total_bytes_allocated is just used for debugging / reporting and since
140 * the mempool.c functions are called quite often, a discussion led the the conclusion of ignoring these races:
141 * https://bugzilla.xamarin.com/show_bug.cgi?id=57936
143 * \param pool the memory pool to destroy
145 * Free all memory associated with this pool.
147 MONO_NO_SANITIZE_THREAD
149 mono_mempool_destroy (MonoMemPool *pool)
153 total_bytes_allocated -= pool->d.allocated;
164 * mono_mempool_invalidate:
165 * \param pool the memory pool to invalidate
167 * Fill the memory associated with this pool to 0x2a (42). Useful for debugging.
170 mono_mempool_invalidate (MonoMemPool *pool)
177 memset (p, 42, p->size);
183 * mono_mempool_stats:
184 * \param pool the memory pool we need stats for
186 * Print a few stats about the mempool:
187 * - Total memory allocated (malloced) by mem pool
188 * - Number of chunks/blocks memory is allocated in
189 * - How much memory is available to dispense before a new malloc must occur?
192 mono_mempool_stats (MonoMemPool *pool)
204 still_free = pool->end - pool->pos;
205 g_print ("Mempool %p stats:\n", pool);
206 g_print ("Total mem allocated: %d\n", pool->d.allocated);
207 g_print ("Num chunks: %d\n", count);
208 g_print ("Free memory: %d\n", still_free);
212 #ifdef TRACE_ALLOCATIONS
213 #include <execinfo.h>
214 #include "metadata/appdomain.h"
215 #include "metadata/metadata-internals.h"
217 static mono_mutex_t mempool_tracing_lock;
218 #define BACKTRACE_DEPTH 7
220 mono_backtrace (int size)
222 void *array[BACKTRACE_DEPTH];
225 static gboolean inited;
228 mono_os_mutex_init_recursive (&mempool_tracing_lock);
232 mono_os_mutex_lock (&mempool_tracing_lock);
233 g_print ("Allocating %d bytes\n", size);
235 symbols = backtrace (array, BACKTRACE_DEPTH);
236 names = backtrace_symbols (array, symbols);
238 for (i = 1; i < symbols; ++i) {
239 g_print ("\t%s\n", names [i]);
242 mono_os_mutex_unlock (&mempool_tracing_lock);
249 * @pool: the memory pool to use
250 * @size: size of the memory entity we are trying to allocate
252 * A mempool is growing; give a recommended size for the next block.
253 * Each block in a mempool should be about 150% bigger than the previous one,
254 * or bigger if it is necessary to include the new entity.
256 * Returns: the recommended size.
259 get_next_size (MonoMemPool *pool, int size)
261 int target = pool->next? pool->next->size: pool->size;
262 size += SIZEOF_MEM_POOL;
263 /* increase the size */
264 target += target / 2;
265 while (target < size) {
266 target += target / 2;
268 if (target > MONO_MEMPOOL_PAGESIZE && size <= MONO_MEMPOOL_PAGESIZE)
269 target = MONO_MEMPOOL_PAGESIZE;
274 * mono_mempool_alloc:
276 * clang's ThreadSanitizer detects races of total_bytes_allocated and pool->d.allocated throughout the functions
277 * * mono_mempool_alloc
278 * * mono_mempool_new_size
279 * * mono_mempool_destroy
280 * while these races could lead to wrong values, total_bytes_allocated is just used for debugging / reporting and since
281 * the mempool.c functions are called quite often, a discussion led the the conclusion of ignoring these races:
282 * https://bugzilla.xamarin.com/show_bug.cgi?id=57936
284 * \param pool the memory pool to use
285 * \param size size of the memory block
287 * Allocates a new block of memory in \p pool .
289 * \returns the address of a newly allocated memory block.
291 MONO_NO_SANITIZE_THREAD
293 mono_mempool_alloc (MonoMemPool *pool, guint size)
295 gpointer rval = pool->pos; // Return value
297 // Normal case: Just bump up pos pointer and we are done
298 size = ALIGN_SIZE (size);
299 pool->pos = (guint8*)rval + size;
301 #ifdef TRACE_ALLOCATIONS
302 if (pool == mono_get_corlib ()->mempool) {
303 mono_backtrace (size);
307 // If we have just overflowed the current block, we need to back up and try again.
308 if (G_UNLIKELY (pool->pos >= pool->end)) {
309 pool->pos -= size; // Back out
311 // For large objects, allocate the object into its own block.
312 // (In individual allocation mode, the constant will be 0 and this path will always be taken)
313 if (size >= MONO_MEMPOOL_PREFER_INDIVIDUAL_ALLOCATION_SIZE) {
314 guint new_size = SIZEOF_MEM_POOL + size;
315 MonoMemPool *np = (MonoMemPool *)g_malloc (new_size);
317 np->next = pool->next;
320 pool->d.allocated += new_size;
321 total_bytes_allocated += new_size;
323 rval = (guint8*)np + SIZEOF_MEM_POOL;
325 // Notice: any unused memory at the end of the old head becomes simply abandoned in this case until the mempool is freed (see Bugzilla #35136)
326 guint new_size = get_next_size (pool, size);
327 MonoMemPool *np = (MonoMemPool *)g_malloc (new_size);
329 np->next = pool->next;
332 pool->pos = (guint8*)np + SIZEOF_MEM_POOL;
333 pool->end = (guint8*)np + new_size;
334 pool->d.allocated += new_size;
335 total_bytes_allocated += new_size;
346 * mono_mempool_alloc0:
348 * same as \c mono_mempool_alloc, but fills memory with zero.
351 mono_mempool_alloc0 (MonoMemPool *pool, guint size)
355 // For the fast path, repeat the first few lines of mono_mempool_alloc
356 size = ALIGN_SIZE (size);
358 pool->pos = (guint8*)rval + size;
360 // If that doesn't work fall back on mono_mempool_alloc to handle new chunk allocation
361 if (G_UNLIKELY (pool->pos >= pool->end)) {
362 rval = mono_mempool_alloc (pool, size);
364 #ifdef TRACE_ALLOCATIONS
365 else if (pool == mono_get_corlib ()->mempool) {
366 mono_backtrace (size);
370 memset (rval, 0, size);
375 * mono_mempool_contains_addr:
377 * Determines whether \p addr is inside the memory used by the mempool.
380 mono_mempool_contains_addr (MonoMemPool *pool,
383 MonoMemPool *p = pool;
386 if (addr >= (gpointer)p && addr < (gpointer)((guint8*)p + p->size))
395 * mono_mempool_strdup:
397 * Same as strdup, but allocates memory from the mempool.
398 * Returns: a pointer to the newly allocated string data inside the mempool.
401 mono_mempool_strdup (MonoMemPool *pool,
411 res = (char *)mono_mempool_alloc (pool, l + 1);
412 memcpy (res, s, l + 1);
418 mono_mempool_strdup_vprintf (MonoMemPool *pool, const char *format, va_list args)
423 va_copy (args2, args);
424 int len = vsnprintf (NULL, 0, format, args2);
427 if (len >= 0 && (buf = (char*)mono_mempool_alloc (pool, (buflen = (size_t) (len + 1)))) != NULL) {
428 vsnprintf (buf, buflen, format, args);
436 mono_mempool_strdup_printf (MonoMemPool *pool, const char *format, ...)
440 va_start (args, format);
441 buf = mono_mempool_strdup_vprintf (pool, format, args);
447 * mono_mempool_get_allocated:
449 * Return the amount of memory allocated for this mempool.
452 mono_mempool_get_allocated (MonoMemPool *pool)
454 return pool->d.allocated;
458 * mono_mempool_get_bytes_allocated:
460 * Return the number of bytes currently allocated for mempools.
463 mono_mempool_get_bytes_allocated (void)
465 return total_bytes_allocated;