/** * \file * Nursery allocation code. * * Copyright 2009-2010 Novell, Inc. * 2011 Rodrigo Kumpera * * Copyright 2011 Xamarin Inc (http://www.xamarin.com) * Copyright (C) 2012 Xamarin Inc * * Licensed under the MIT license. See LICENSE file in the project root for full license information. */ /* * The young generation is divided into fragments. This is because * we can hand one fragments to a thread for lock-less fast alloc and * because the young generation ends up fragmented anyway by pinned objects. * Once a collection is done, a list of fragments is created. When doing * thread local alloc we use smallish nurseries so we allow new threads to * allocate memory from gen0 without triggering a collection. Threads that * are found to allocate lots of memory are given bigger fragments. This * should make the finalizer thread use little nursery memory after a while. * We should start assigning threads very small fragments: if there are many * threads the nursery will be full of reserved space that the threads may not * use at all, slowing down allocation speed. * Thread local allocation is done from areas of memory Hotspot calls Thread Local * Allocation Buffers (TLABs). */ #include "config.h" #ifdef HAVE_SGEN_GC #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_PTHREAD_H #include #endif #ifdef HAVE_SEMAPHORE_H #include #endif #include #include #include #include #ifdef __MACH__ #undef _XOPEN_SOURCE #endif #ifdef __MACH__ #define _XOPEN_SOURCE #endif #include "mono/sgen/sgen-gc.h" #include "mono/sgen/sgen-cardtable.h" #include "mono/sgen/sgen-protocol.h" #include "mono/sgen/sgen-memory-governor.h" #include "mono/sgen/sgen-pinning.h" #include "mono/sgen/sgen-client.h" #include "mono/utils/mono-membar.h" /* Enable it so nursery allocation diagnostic data is collected */ //#define NALLOC_DEBUG 1 /* The mutator allocs from here. */ static SgenFragmentAllocator mutator_allocator; /* freeelist of fragment structures */ static SgenFragment *fragment_freelist = NULL; char *sgen_nursery_start; char *sgen_nursery_end; /* good sizes are 512KB-1MB: larger ones increase a lot memzeroing time */ size_t sgen_nursery_size; /* * Maximum size that we can resize the nursery to. * If sgen_nursery_default_size == sgen_nursery_max_size then we are not * dynamically resizing the nursery */ size_t sgen_nursery_max_size; size_t sgen_nursery_min_size; /* The number of trailing 0 bits in sgen_nursery_max_size */ int sgen_nursery_bits; char *sgen_space_bitmap; size_t sgen_space_bitmap_size; #ifdef HEAVY_STATISTICS static mword stat_wasted_bytes_trailer = 0; static mword stat_wasted_bytes_small_areas = 0; static mword stat_wasted_bytes_discarded_fragments = 0; static guint64 stat_nursery_alloc_requests = 0; static guint64 stat_alloc_iterations = 0; static guint64 stat_alloc_retries = 0; static guint64 stat_nursery_alloc_range_requests = 0; static guint64 stat_alloc_range_iterations = 0; static guint64 stat_alloc_range_retries = 0; #endif /************************************Nursery allocation debugging *********************************************/ #ifdef NALLOC_DEBUG enum { FIXED_ALLOC = 1, RANGE_ALLOC, PINNING, BLOCK_ZEROING, CLEAR_NURSERY_FRAGS }; typedef struct { char *address; size_t size; int reason; int seq; MonoNativeThreadId tid; } AllocRecord; #define ALLOC_RECORD_COUNT 128000 static AllocRecord *alloc_records; static volatile int next_record; static volatile int alloc_count; void dump_alloc_records (void); void verify_alloc_records (void); static const char* get_reason_name (AllocRecord *rec) { switch (rec->reason) { case FIXED_ALLOC: return "fixed-alloc"; case RANGE_ALLOC: return "range-alloc"; case PINNING: return "pinning"; case BLOCK_ZEROING: return "block-zeroing"; case CLEAR_NURSERY_FRAGS: return "clear-nursery-frag"; default: return "invalid"; } } static void reset_alloc_records (void) { next_record = 0; alloc_count = 0; } static void add_alloc_record (char *addr, size_t size, int reason) { int idx = InterlockedIncrement (&next_record) - 1; alloc_records [idx].address = addr; alloc_records [idx].size = size; alloc_records [idx].reason = reason; alloc_records [idx].seq = idx; alloc_records [idx].tid = mono_native_thread_id_get (); } static int comp_alloc_record (const void *_a, const void *_b) { const AllocRecord *a = _a; const AllocRecord *b = _b; if (a->address == b->address) return a->seq - b->seq; return a->address - b->address; } #define rec_end(REC) ((REC)->address + (REC)->size) void dump_alloc_records (void) { int i; sgen_qsort (alloc_records, next_record, sizeof (AllocRecord), comp_alloc_record); printf ("------------------------------------DUMP RECORDS----------------------------\n"); for (i = 0; i < next_record; ++i) { AllocRecord *rec = alloc_records + i; printf ("obj [%p, %p] size %d reason %s seq %d tid %x\n", rec->address, rec_end (rec), (int)rec->size, get_reason_name (rec), rec->seq, (size_t)rec->tid); } } void verify_alloc_records (void) { int i; int total = 0; int holes = 0; int max_hole = 0; AllocRecord *prev = NULL; sgen_qsort (alloc_records, next_record, sizeof (AllocRecord), comp_alloc_record); printf ("------------------------------------DUMP RECORDS- %d %d---------------------------\n", next_record, alloc_count); for (i = 0; i < next_record; ++i) { AllocRecord *rec = alloc_records + i; int hole_size = 0; total += rec->size; if (prev) { if (rec_end (prev) > rec->address) printf ("WE GOT OVERLAPPING objects %p and %p\n", prev->address, rec->address); if ((rec->address - rec_end (prev)) >= 8) ++holes; hole_size = rec->address - rec_end (prev); max_hole = MAX (max_hole, hole_size); } printf ("obj [%p, %p] size %d hole to prev %d reason %s seq %d tid %zx\n", rec->address, rec_end (rec), (int)rec->size, hole_size, get_reason_name (rec), rec->seq, (size_t)rec->tid); prev = rec; } printf ("SUMMARY total alloc'd %d holes %d max_hole %d\n", total, holes, max_hole); } #endif /*********************************************************************************/ static inline gpointer mask (gpointer n, uintptr_t bit) { return (gpointer)(((uintptr_t)n) | bit); } static inline gpointer unmask (gpointer p) { return (gpointer)((uintptr_t)p & ~(uintptr_t)0x3); } static inline uintptr_t get_mark (gpointer n) { return (uintptr_t)n & 0x1; } /*MUST be called with world stopped*/ SgenFragment* sgen_fragment_allocator_alloc (void) { SgenFragment *frag = fragment_freelist; if (frag) { fragment_freelist = frag->next_in_order; frag->next = frag->next_in_order = NULL; return frag; } frag = (SgenFragment *)sgen_alloc_internal (INTERNAL_MEM_FRAGMENT); frag->next = frag->next_in_order = NULL; return frag; } void sgen_fragment_allocator_add (SgenFragmentAllocator *allocator, char *start, char *end) { SgenFragment *fragment; fragment = sgen_fragment_allocator_alloc (); fragment->fragment_start = start; fragment->fragment_next = start; fragment->fragment_end = end; fragment->next_in_order = fragment->next = (SgenFragment *)unmask (allocator->region_head); allocator->region_head = allocator->alloc_head = fragment; g_assert (fragment->fragment_end > fragment->fragment_start); } void sgen_fragment_allocator_release (SgenFragmentAllocator *allocator) { SgenFragment *last = allocator->region_head; if (!last) return; /* Find the last fragment in insert order */ for (; last->next_in_order; last = last->next_in_order) ; last->next_in_order = fragment_freelist; fragment_freelist = allocator->region_head; allocator->alloc_head = allocator->region_head = NULL; } static SgenFragment** find_previous_pointer_fragment (SgenFragmentAllocator *allocator, SgenFragment *frag) { SgenFragment **prev; SgenFragment *cur, *next; #ifdef NALLOC_DEBUG int count = 0; #endif try_again: prev = &allocator->alloc_head; #ifdef NALLOC_DEBUG if (count++ > 5) printf ("retry count for fppf is %d\n", count); #endif cur = (SgenFragment *)unmask (*prev); while (1) { if (cur == NULL) return NULL; next = cur->next; /* * We need to make sure that we dereference prev below * after reading cur->next above, so we need a read * barrier. */ mono_memory_read_barrier (); if (*prev != cur) goto try_again; if (!get_mark (next)) { if (cur == frag) return prev; prev = &cur->next; } else { next = (SgenFragment *)unmask (next); if (InterlockedCompareExchangePointer ((volatile gpointer*)prev, next, cur) != cur) goto try_again; /*we must make sure that the next from cur->next happens after*/ mono_memory_write_barrier (); } cur = (SgenFragment *)unmask (next); } return NULL; } static gboolean claim_remaining_size (SgenFragment *frag, char *alloc_end) { /* All space used, nothing to claim. */ if (frag->fragment_end <= alloc_end) return FALSE; /* Try to alloc all the remaining space. */ return InterlockedCompareExchangePointer ((volatile gpointer*)&frag->fragment_next, frag->fragment_end, alloc_end) == alloc_end; } static void* par_alloc_from_fragment (SgenFragmentAllocator *allocator, SgenFragment *frag, size_t size) { char *p = frag->fragment_next; char *end = p + size; if (end > frag->fragment_end || end > (sgen_nursery_start + sgen_nursery_size)) return NULL; /* p = frag->fragment_next must happen before */ mono_memory_barrier (); if (InterlockedCompareExchangePointer ((volatile gpointer*)&frag->fragment_next, end, p) != p) return NULL; if (frag->fragment_end - end < SGEN_MAX_NURSERY_WASTE) { SgenFragment *next, **prev_ptr; /* * Before we clean the remaining nursery, we must claim the remaining space * as it could end up been used by the range allocator since it can end up * allocating from this dying fragment as it doesn't respect SGEN_MAX_NURSERY_WASTE * when doing second chance allocation. */ if ((sgen_get_nursery_clear_policy () == CLEAR_AT_TLAB_CREATION || sgen_get_nursery_clear_policy () == CLEAR_AT_TLAB_CREATION_DEBUG) && claim_remaining_size (frag, end)) { sgen_clear_range (end, frag->fragment_end); HEAVY_STAT (stat_wasted_bytes_trailer += frag->fragment_end - end); #ifdef NALLOC_DEBUG add_alloc_record (end, frag->fragment_end - end, BLOCK_ZEROING); #endif } prev_ptr = find_previous_pointer_fragment (allocator, frag); /*Use Michaels linked list remove*/ /*prev_ptr will be null if the fragment was removed concurrently */ while (prev_ptr) { next = frag->next; /*already deleted*/ if (!get_mark (next)) { /*frag->next read must happen before the first CAS*/ mono_memory_write_barrier (); /*Fail if the next node is removed concurrently and its CAS wins */ if (InterlockedCompareExchangePointer ((volatile gpointer*)&frag->next, mask (next, 1), next) != next) { continue; } } /* The second CAS must happen after the first CAS or frag->next. */ mono_memory_write_barrier (); /* Fail if the previous node was deleted and its CAS wins */ if (InterlockedCompareExchangePointer ((volatile gpointer*)prev_ptr, unmask (next), frag) != frag) { prev_ptr = find_previous_pointer_fragment (allocator, frag); continue; } break; } } return p; } static void* serial_alloc_from_fragment (SgenFragment **previous, SgenFragment *frag, size_t size) { char *p = frag->fragment_next; char *end = p + size; if (end > frag->fragment_end) return NULL; frag->fragment_next = end; if (frag->fragment_end - end < SGEN_MAX_NURSERY_WASTE) { *previous = frag->next; /* Clear the remaining space, pinning depends on this. FIXME move this to use phony arrays */ memset (end, 0, frag->fragment_end - end); *previous = frag->next; } return p; } void* sgen_fragment_allocator_par_alloc (SgenFragmentAllocator *allocator, size_t size) { SgenFragment *frag; #ifdef NALLOC_DEBUG InterlockedIncrement (&alloc_count); #endif restart: for (frag = (SgenFragment *)unmask (allocator->alloc_head); unmask (frag); frag = (SgenFragment *)unmask (frag->next)) { size_t frag_size = frag->fragment_end - frag->fragment_next; if (frag->fragment_next >= (sgen_nursery_start + sgen_nursery_size)) continue; HEAVY_STAT (++stat_alloc_iterations); if (size <= frag_size) { void *p = par_alloc_from_fragment (allocator, frag, size); if (!p) { HEAVY_STAT (++stat_alloc_retries); goto restart; } #ifdef NALLOC_DEBUG add_alloc_record (p, size, FIXED_ALLOC); #endif return p; } } return NULL; } void* sgen_fragment_allocator_serial_range_alloc (SgenFragmentAllocator *allocator, size_t desired_size, size_t minimum_size, size_t *out_alloc_size) { SgenFragment *frag, **previous, *min_frag = NULL, **prev_min_frag = NULL; size_t current_minimum = minimum_size; #ifdef NALLOC_DEBUG InterlockedIncrement (&alloc_count); #endif previous = &allocator->alloc_head; for (frag = *previous; frag; frag = *previous) { size_t frag_size = frag->fragment_end - frag->fragment_next; HEAVY_STAT (++stat_alloc_range_iterations); if (desired_size <= frag_size) { void *p; *out_alloc_size = desired_size; p = serial_alloc_from_fragment (previous, frag, desired_size); #ifdef NALLOC_DEBUG add_alloc_record (p, desired_size, RANGE_ALLOC); #endif return p; } if (current_minimum <= frag_size) { min_frag = frag; prev_min_frag = previous; current_minimum = frag_size; } previous = &frag->next; } if (min_frag) { void *p; size_t frag_size = min_frag->fragment_end - min_frag->fragment_next; *out_alloc_size = frag_size; p = serial_alloc_from_fragment (prev_min_frag, min_frag, frag_size); #ifdef NALLOC_DEBUG add_alloc_record (p, frag_size, RANGE_ALLOC); #endif return p; } return NULL; } void* sgen_fragment_allocator_par_range_alloc (SgenFragmentAllocator *allocator, size_t desired_size, size_t minimum_size, size_t *out_alloc_size) { SgenFragment *frag, *min_frag; size_t current_minimum; restart: min_frag = NULL; current_minimum = minimum_size; #ifdef NALLOC_DEBUG InterlockedIncrement (&alloc_count); #endif for (frag = (SgenFragment *)unmask (allocator->alloc_head); frag; frag = (SgenFragment *)unmask (frag->next)) { size_t frag_size = frag->fragment_end - frag->fragment_next; if (frag->fragment_next >= (sgen_nursery_start + sgen_nursery_size)) continue; HEAVY_STAT (++stat_alloc_range_iterations); if (desired_size <= frag_size) { void *p; *out_alloc_size = desired_size; p = par_alloc_from_fragment (allocator, frag, desired_size); if (!p) { HEAVY_STAT (++stat_alloc_range_retries); goto restart; } #ifdef NALLOC_DEBUG add_alloc_record (p, desired_size, RANGE_ALLOC); #endif return p; } if (current_minimum <= frag_size) { min_frag = frag; current_minimum = frag_size; } } /* The second fragment_next read should be ordered in respect to the first code block */ mono_memory_barrier (); if (min_frag) { void *p; size_t frag_size = min_frag->fragment_end - min_frag->fragment_next; if (frag_size < minimum_size) goto restart; *out_alloc_size = frag_size; mono_memory_barrier (); p = par_alloc_from_fragment (allocator, min_frag, frag_size); /*XXX restarting here is quite dubious given this is already second chance allocation. */ if (!p) { HEAVY_STAT (++stat_alloc_retries); goto restart; } #ifdef NALLOC_DEBUG add_alloc_record (p, frag_size, RANGE_ALLOC); #endif return p; } return NULL; } void sgen_clear_allocator_fragments (SgenFragmentAllocator *allocator) { SgenFragment *frag; for (frag = (SgenFragment *)unmask (allocator->alloc_head); frag; frag = (SgenFragment *)unmask (frag->next)) { SGEN_LOG (4, "Clear nursery frag %p-%p", frag->fragment_next, frag->fragment_end); sgen_clear_range (frag->fragment_next, frag->fragment_end); #ifdef NALLOC_DEBUG add_alloc_record (frag->fragment_next, frag->fragment_end - frag->fragment_next, CLEAR_NURSERY_FRAGS); #endif } } /* Clear all remaining nursery fragments */ void sgen_clear_nursery_fragments (void) { if (sgen_get_nursery_clear_policy () == CLEAR_AT_TLAB_CREATION || sgen_get_nursery_clear_policy () == CLEAR_AT_TLAB_CREATION_DEBUG) { sgen_clear_allocator_fragments (&mutator_allocator); sgen_minor_collector.clear_fragments (); } } /* * Mark a given range of memory as invalid. * * This can be done either by zeroing memory or by placing * a phony byte[] array. This keeps the heap forward walkable. * * This function ignores calls with a zero range, even if * both start and end are NULL. */ void sgen_clear_range (char *start, char *end) { size_t size = end - start; if ((start && !end) || (start > end)) g_error ("Invalid range [%p %p]", start, end); if (sgen_client_array_fill_range (start, size)) { sgen_set_nursery_scan_start (start); SGEN_ASSERT (0, start + sgen_safe_object_get_size ((GCObject*)start) == end, "Array fill produced wrong size"); } } void sgen_nursery_allocator_prepare_for_pinning (void) { sgen_clear_allocator_fragments (&mutator_allocator); sgen_minor_collector.clear_fragments (); } static mword fragment_total = 0; /* * We found a fragment of free memory in the nursery: memzero it and if * it is big enough, add it to the list of fragments that can be used for * allocation. */ static void add_nursery_frag (SgenFragmentAllocator *allocator, size_t frag_size, char* frag_start, char* frag_end) { SGEN_LOG (4, "Found empty fragment: %p-%p, size: %zd", frag_start, frag_end, frag_size); binary_protocol_empty (frag_start, frag_size); /* Not worth dealing with smaller fragments: need to tune */ if (frag_size >= SGEN_MAX_NURSERY_WASTE) { /* memsetting just the first chunk start is bound to provide better cache locality */ if (sgen_get_nursery_clear_policy () == CLEAR_AT_GC) memset (frag_start, 0, frag_size); else if (sgen_get_nursery_clear_policy () == CLEAR_AT_TLAB_CREATION_DEBUG) memset (frag_start, 0xff, frag_size); #ifdef NALLOC_DEBUG /* XXX convert this into a flight record entry printf ("\tfragment [%p %p] size %zd\n", frag_start, frag_end, frag_size); */ #endif sgen_fragment_allocator_add (allocator, frag_start, frag_end); fragment_total += frag_size; } else { /* Clear unused fragments, pinning depends on this */ sgen_clear_range (frag_start, frag_end); HEAVY_STAT (stat_wasted_bytes_small_areas += frag_size); } } static void fragment_list_reverse (SgenFragmentAllocator *allocator) { SgenFragment *prev = NULL, *list = allocator->region_head; while (list) { SgenFragment *next = list->next; list->next = prev; list->next_in_order = prev; prev = list; list = next; } allocator->region_head = allocator->alloc_head = prev; } /* * We split fragments at the border of the current nursery limit. When we * allocate from the nursery we only consider fragments that start in the * current nursery section. We build fragments for the entire nursery in * order to facilitate scanning it for objects (adding a nursery frag also * marks a region in the nursery as being free) */ static void add_nursery_frag_checks (SgenFragmentAllocator *allocator, char *frag_start, char *frag_end) { char *nursery_limit = sgen_nursery_start + sgen_nursery_size; if (frag_start < nursery_limit && frag_end > nursery_limit) { add_nursery_frag (allocator, nursery_limit - frag_start, frag_start, nursery_limit); add_nursery_frag (allocator, frag_end - nursery_limit, nursery_limit, frag_end); } else { add_nursery_frag (allocator, frag_end - frag_start, frag_start, frag_end); } } mword sgen_build_nursery_fragments (GCMemSection *nursery_section, SgenGrayQueue *unpin_queue) { char *frag_start, *frag_end; size_t frag_size; SgenFragment *frags_ranges; void **pin_start, **pin_entry, **pin_end; #ifdef NALLOC_DEBUG reset_alloc_records (); #endif /*The mutator fragments are done. We no longer need them. */ sgen_fragment_allocator_release (&mutator_allocator); frag_start = sgen_nursery_start; fragment_total = 0; /* The current nursery might give us a fragment list to exclude [start, next[*/ frags_ranges = sgen_minor_collector.build_fragments_get_exclude_head (); /* clear scan starts */ memset (nursery_section->scan_starts, 0, nursery_section->num_scan_start * sizeof (gpointer)); pin_start = pin_entry = sgen_pinning_get_entry (nursery_section->pin_queue_first_entry); pin_end = sgen_pinning_get_entry (nursery_section->pin_queue_last_entry); while (pin_entry < pin_end || frags_ranges) { char *addr0, *addr1; size_t size; addr0 = addr1 = sgen_nursery_end; if (pin_entry < pin_end) addr0 = (char *)*pin_entry; if (frags_ranges) addr1 = frags_ranges->fragment_start; if (addr0 < addr1) { if (unpin_queue) GRAY_OBJECT_ENQUEUE_SERIAL (unpin_queue, (GCObject*)addr0, sgen_obj_get_descriptor_safe ((GCObject*)addr0)); else SGEN_UNPIN_OBJECT (addr0); size = SGEN_ALIGN_UP (sgen_safe_object_get_size ((GCObject*)addr0)); CANARIFY_SIZE (size); sgen_set_nursery_scan_start (addr0); frag_end = addr0; ++pin_entry; } else { frag_end = addr1; size = frags_ranges->fragment_next - addr1; frags_ranges = frags_ranges->next_in_order; } frag_size = frag_end - frag_start; if (size == 0) continue; g_assert (frag_size >= 0); g_assert (size > 0); if (frag_size && size) add_nursery_frag_checks (&mutator_allocator, frag_start, frag_end); frag_size = size; #ifdef NALLOC_DEBUG add_alloc_record (*pin_entry, frag_size, PINNING); #endif frag_start = frag_end + frag_size; } frag_end = sgen_nursery_end; frag_size = frag_end - frag_start; if (frag_size) add_nursery_frag_checks (&mutator_allocator, frag_start, frag_end); /* Now it's safe to release the fragments exclude list. */ sgen_minor_collector.build_fragments_release_exclude_head (); /* First we reorder the fragment list to be in ascending address order. This makes H/W prefetchers happier. */ fragment_list_reverse (&mutator_allocator); /*The collector might want to do something with the final nursery fragment list.*/ sgen_minor_collector.build_fragments_finish (&mutator_allocator); if (!unmask (mutator_allocator.alloc_head)) { SGEN_LOG (1, "Nursery fully pinned"); for (pin_entry = pin_start; pin_entry < pin_end; ++pin_entry) { GCObject *p = (GCObject *)*pin_entry; SGEN_LOG (3, "Bastard pinning obj %p (%s), size: %zd", p, sgen_client_vtable_get_name (SGEN_LOAD_VTABLE (p)), sgen_safe_object_get_size (p)); } } return fragment_total; } /*** Nursery memory allocation ***/ void sgen_nursery_retire_region (void *address, ptrdiff_t size) { HEAVY_STAT (stat_wasted_bytes_discarded_fragments += size); } gboolean sgen_can_alloc_size (size_t size) { SgenFragment *frag; if (!SGEN_CAN_ALIGN_UP (size)) return FALSE; size = SGEN_ALIGN_UP (size); for (frag = (SgenFragment *)unmask (mutator_allocator.alloc_head); frag; frag = (SgenFragment *)unmask (frag->next)) { if ((size_t)(frag->fragment_end - frag->fragment_next) >= size) return TRUE; } return FALSE; } void* sgen_nursery_alloc (size_t size) { SGEN_ASSERT (1, size >= (SGEN_CLIENT_MINIMUM_OBJECT_SIZE + CANARY_SIZE) && size <= (SGEN_MAX_SMALL_OBJ_SIZE + CANARY_SIZE), "Invalid nursery object size"); SGEN_LOG (4, "Searching nursery for size: %zd", size); size = SGEN_ALIGN_UP (size); HEAVY_STAT (++stat_nursery_alloc_requests); return sgen_fragment_allocator_par_alloc (&mutator_allocator, size); } void* sgen_nursery_alloc_range (size_t desired_size, size_t minimum_size, size_t *out_alloc_size) { SGEN_LOG (4, "Searching for byte range desired size: %zd minimum size %zd", desired_size, minimum_size); HEAVY_STAT (++stat_nursery_alloc_range_requests); return sgen_fragment_allocator_par_range_alloc (&mutator_allocator, desired_size, minimum_size, out_alloc_size); } /*** Initialization ***/ #ifdef HEAVY_STATISTICS void sgen_nursery_allocator_init_heavy_stats (void) { mono_counters_register ("bytes wasted trailer fragments", MONO_COUNTER_GC | MONO_COUNTER_WORD | MONO_COUNTER_BYTES, &stat_wasted_bytes_trailer); mono_counters_register ("bytes wasted small areas", MONO_COUNTER_GC | MONO_COUNTER_WORD | MONO_COUNTER_BYTES, &stat_wasted_bytes_small_areas); mono_counters_register ("bytes wasted discarded fragments", MONO_COUNTER_GC | MONO_COUNTER_WORD | MONO_COUNTER_BYTES, &stat_wasted_bytes_discarded_fragments); mono_counters_register ("# nursery alloc requests", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_nursery_alloc_requests); mono_counters_register ("# nursery alloc iterations", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_alloc_iterations); mono_counters_register ("# nursery alloc retries", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_alloc_retries); mono_counters_register ("# nursery alloc range requests", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_nursery_alloc_range_requests); mono_counters_register ("# nursery alloc range iterations", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_alloc_range_iterations); mono_counters_register ("# nursery alloc range restries", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_alloc_range_retries); } #endif void sgen_init_nursery_allocator (void) { sgen_register_fixed_internal_mem_type (INTERNAL_MEM_FRAGMENT, sizeof (SgenFragment)); #ifdef NALLOC_DEBUG alloc_records = sgen_alloc_os_memory (sizeof (AllocRecord) * ALLOC_RECORD_COUNT, SGEN_ALLOC_INTERNAL | SGEN_ALLOC_ACTIVATE, "debugging memory"); #endif } void sgen_nursery_alloc_prepare_for_minor (void) { sgen_minor_collector.prepare_to_space (sgen_space_bitmap, sgen_space_bitmap_size); } void sgen_nursery_alloc_prepare_for_major (void) { sgen_minor_collector.prepare_to_space (sgen_space_bitmap, sgen_space_bitmap_size); } void sgen_nursery_allocator_set_nursery_bounds (char *start, size_t min_size, size_t max_size) { sgen_nursery_start = start; sgen_nursery_end = start + max_size; sgen_nursery_size = min_size; sgen_nursery_min_size = min_size; sgen_nursery_max_size = max_size; sgen_nursery_bits = 0; while (ONE_P << (++ sgen_nursery_bits) != sgen_nursery_max_size) ; /* * This will not divide evenly for tiny nurseries (<4kb), so we make sure to be on * the right side of things and round up. We could just do a MIN(1,x) instead, * since the nursery size must be a power of 2. */ sgen_space_bitmap_size = (sgen_nursery_end - sgen_nursery_start + SGEN_TO_SPACE_GRANULE_IN_BYTES * 8 - 1) / (SGEN_TO_SPACE_GRANULE_IN_BYTES * 8); sgen_space_bitmap = (char *)g_malloc0 (sgen_space_bitmap_size); /* Setup the single first large fragment */ sgen_minor_collector.init_nursery (&mutator_allocator, sgen_nursery_start, sgen_nursery_end); } void sgen_resize_nursery (gboolean need_shrink) { size_t major_size; if (sgen_nursery_min_size == sgen_nursery_max_size) return; major_size = major_collector.get_num_major_sections () * major_collector.section_size + los_memory_usage; /* * We attempt to use a larger nursery size, as long as it doesn't * exceed a certain percentage of the major heap. * * FIXME * Commit memory when expanding and release it when shrinking (which * would only be possible if there aren't any pinned objects in the * section). */ if ((sgen_nursery_size * 2) < (major_size / SGEN_DEFAULT_ALLOWANCE_NURSERY_SIZE_RATIO) && (sgen_nursery_size * 2) <= sgen_nursery_max_size && !need_shrink) { if ((nursery_section->end_data - nursery_section->data) == sgen_nursery_size) nursery_section->end_data += sgen_nursery_size; sgen_nursery_size *= 2; } else if ((sgen_nursery_size > (major_size / SGEN_DEFAULT_ALLOWANCE_NURSERY_SIZE_RATIO) || need_shrink) && (sgen_nursery_size / 2) >= sgen_nursery_min_size) { sgen_nursery_size /= 2; } } #endif