[sgen] Improve memory usage with the concurrent collector
authorVlad Brezae <brezaevlad@gmail.com>
Wed, 3 Feb 2016 22:54:12 +0000 (00:54 +0200)
committerVlad Brezae <brezaevlad@gmail.com>
Mon, 8 Feb 2016 14:22:12 +0000 (16:22 +0200)
The concurrent collector typically uses more memory because more memory is allocated during the concurrent phase, which also leads to more objects surviving the major collections and higher allowances. We improved the memory situation by forcing synchronous finishing of the concurrent mark if too much memory is allocated during the concurrent mark phase. Still, this doesn't tackle the underlying problem. Even if we have less memory allocated during the CMS phase, some is still allocated and leads to increased overall memory usage. And how much memory growth should we allow in the first place ? The even greater problem is that it appears to force a lot of synchronous finishes, significantly increasing finishing pause times.

This commit attempts to solve this problem by following an ideal mathematical approach. We use the following notations : H - heap size at start of major collection, Gs - heap growth during sync collection, Gc - heap growth during conc collection, s - survival rate of the collection, a - allowance ratio (0.33), Ts - trigger for next major sync, Tc - trigger for next major conc.

Assume we have the heap size H at the start of a major collection. For the two types of majors we would have :
Ts = (H + Gs) * s * (a + 1)
Tc = (H + Gc) * s * (a + 1)

Obviously the problem is that Gc > Gs, which leads to Tc > Ts and higher memory usage. In order to have the two triggers equal we need to find an X that we can subtract from Tc to obtain Ts. Considering Gs = 0 (only a few nursery objects are promoted during a major sync collection) and similar survival rates for the two types of collection, we get X = Gc * s * (a + 1). In order to get equal triggers we would need to subtract this value from the allowance computed after a concurrent collection.

The more the heap grows, the more X grows. If X gets bigger than the available starting allowance ((H + Gc) * s * a), it would mean that we are trying to set a trigger that it is lower than the actual heap size at the end of the collection (which means we already exceeded the target memory usage). In order to avoid this problem we need to enforce Tc - X > (H + Gc) * s. Doing the substitutions we get Gc < a * H. When the heap growth exceeds this value it means we need to force the finish of the concurrent collection.

mono/sgen/sgen-conf.h
mono/sgen/sgen-gc.c
mono/sgen/sgen-memory-governor.c
mono/sgen/sgen-memory-governor.h

index 5a0e871e22fecc779083f53fea42bc4945f6849a..11a8998478e765e8083e337c57a9f48c6fb7a9ee 100644 (file)
@@ -190,12 +190,6 @@ typedef mword SgenDescriptor;
  */
 #define SGEN_DEFAULT_ALLOWANCE_HEAP_SIZE_RATIO 0.33
 
-/*
- * How much more we allow the heap to grow, relative to the allowance, while doing
- * a concurrent collection, before forcing its finish.
- */
-#define SGEN_DEFAULT_CONCURRENT_HEAP_ALLOWANCE_RATIO 0.25
-
 /*
  * Default ratio of memory we want to release in a major collection in relation to the the current heap size.
  *
index 34da3f15ab339c4b84d83585d4d265832fdc1c24..ae368d6d6d2225b140948d64dabfbfcdb532423a 100644 (file)
@@ -1974,6 +1974,7 @@ major_finish_collection (const char *reason, size_t old_next_pin_slot, gboolean
        time_major_fragment_creation += TV_ELAPSED (atv, btv);
 
        binary_protocol_sweep_begin (GENERATION_OLD, !major_collector.sweeps_lazily);
+       sgen_memgov_major_pre_sweep ();
 
        TV_GETTIME (atv);
        time_major_free_bigobjs += TV_ELAPSED (btv, atv);
index ce34f7f4a67a7d304ff038e674c01aa38651e0c5..611ae5c118f4d66b7e1eb982fd4d32048e78a20b 100644 (file)
@@ -56,6 +56,9 @@ static gboolean debug_print_allowance = FALSE;
 /* use this to tune when to do a major/minor collection */
 static mword major_collection_trigger_size;
 
+static mword major_pre_sweep_heap_size;
+static mword major_start_heap_size;
+
 static mword last_major_num_sections = 0;
 static mword last_los_memory_usage = 0;
 
@@ -73,6 +76,7 @@ static void
 sgen_memgov_calculate_minor_collection_allowance (void)
 {
        size_t new_major, new_heap_size, allowance_target, allowance;
+       size_t decrease;
 
        if (!need_calculate_minor_collection_allowance)
                return;
@@ -90,6 +94,16 @@ sgen_memgov_calculate_minor_collection_allowance (void)
 
        allowance = MAX (allowance_target, MIN_MINOR_COLLECTION_ALLOWANCE);
 
+       /*
+        * For the concurrent collector, we decrease the allowance relative to the memory
+        * growth during the M&S phase, survival rate of the collection and the allowance
+        * ratio.
+        */
+       decrease = (major_pre_sweep_heap_size - major_start_heap_size) * ((float)new_heap_size / major_pre_sweep_heap_size) * (SGEN_DEFAULT_ALLOWANCE_HEAP_SIZE_RATIO + 1);
+       if (decrease > allowance)
+               decrease = allowance;
+       allowance -= decrease;
+
        if (new_heap_size + allowance > soft_heap_limit) {
                if (new_heap_size > soft_heap_limit)
                        allowance = MIN_MINOR_COLLECTION_ALLOWANCE;
@@ -129,13 +143,14 @@ sgen_need_major_collection (mword space_needed)
                if (heap_size <= major_collection_trigger_size)
                        return FALSE; 
 
-               /* We allow the heap to grow an additional third of the allowance during a concurrent collection */
-               if ((heap_size - major_collection_trigger_size) >
-                               (major_collection_trigger_size
-                               * (SGEN_DEFAULT_ALLOWANCE_HEAP_SIZE_RATIO / (SGEN_DEFAULT_ALLOWANCE_HEAP_SIZE_RATIO + 1))
-                               * SGEN_DEFAULT_CONCURRENT_HEAP_ALLOWANCE_RATIO)) {
+               /*
+                * The more the heap grows, the more we need to decrease the allowance above,
+                * in order to have similar trigger sizes as the synchronous collector.
+                * If the heap grows so much that we would need to have a negative allowance,
+                * we force the finishing of the collection, to avoid increased memory usage.
+                */
+               if ((heap_size - major_start_heap_size) > major_start_heap_size * SGEN_DEFAULT_ALLOWANCE_HEAP_SIZE_RATIO)
                        return TRUE;
-               }
                return FALSE;
        }
 
@@ -163,13 +178,25 @@ sgen_memgov_minor_collection_end (void)
 {
 }
 
+void
+sgen_memgov_major_pre_sweep (void)
+{
+       if (sgen_concurrent_collection_in_progress ()) {
+               major_pre_sweep_heap_size = get_heap_size ();
+       } else {
+               /* We decrease the allowance only in the concurrent case */
+               major_pre_sweep_heap_size = major_start_heap_size;
+       }
+}
+
 void
 sgen_memgov_major_collection_start (void)
 {
        need_calculate_minor_collection_allowance = TRUE;
+       major_start_heap_size = get_heap_size ();
 
        if (debug_print_allowance) {
-               SGEN_LOG (0, "Starting collection with heap size %ld bytes", (long)get_heap_size ());
+               SGEN_LOG (0, "Starting collection with heap size %ld bytes", (long)major_start_heap_size);
        }
 }
 
index 0115ec6e0605e2755c4b28dc8c020b89f8ea9466..669b59734e99200ffadae2dc540a7b8137f6349c 100644 (file)
@@ -33,6 +33,7 @@ gboolean sgen_memgov_try_alloc_space (mword size, int space);
 void sgen_memgov_minor_collection_start (void);
 void sgen_memgov_minor_collection_end (void);
 
+void sgen_memgov_major_pre_sweep (void);
 void sgen_memgov_major_collection_start (void);
 void sgen_memgov_major_collection_end (gboolean forced);