Merge pull request #5714 from alexischr/update_bockbuild
[mono.git] / mono / sgen / sgen-memory-governor.c
1 /**
2  * \file
3  * When to schedule collections based on memory usage.
4  *
5  * Author:
6  *      Rodrigo Kumpera (rkumpera@novell.com)
7  *
8  * Copyright 2001-2003 Ximian, Inc
9  * Copyright 2003-2010 Novell, Inc.
10  * Copyright 2011 Xamarin Inc (http://www.xamarin.com)
11  * Copyright (C) 2012 Xamarin Inc
12  *
13  * Licensed under the MIT license. See LICENSE file in the project root for full license information.
14  */
15
16 #include "config.h"
17 #ifdef HAVE_SGEN_GC
18
19 #include <stdlib.h>
20
21 #include "mono/sgen/sgen-gc.h"
22 #include "mono/sgen/sgen-memory-governor.h"
23 #include "mono/sgen/sgen-workers.h"
24 #include "mono/sgen/sgen-client.h"
25
26 #define MIN_MINOR_COLLECTION_ALLOWANCE  ((mword)(SGEN_DEFAULT_NURSERY_SIZE * default_allowance_nursery_size_ratio))
27
28 static SgenPointerQueue log_entries = SGEN_POINTER_QUEUE_INIT (INTERNAL_MEM_TEMPORARY);
29 static mono_mutex_t log_entries_mutex;
30
31 mword total_promoted_size = 0;
32 mword total_allocated_major = 0;
33 static mword total_promoted_size_start;
34 static mword total_allocated_major_end;
35
36 /*Heap limits and allocation knobs*/
37 static mword max_heap_size = ((mword)0)- ((mword)1);
38 static mword soft_heap_limit = ((mword)0) - ((mword)1);
39
40 static double default_allowance_nursery_size_ratio = SGEN_DEFAULT_ALLOWANCE_NURSERY_SIZE_RATIO;
41 static double save_target_ratio = SGEN_DEFAULT_SAVE_TARGET_RATIO;
42
43 /**/
44 static mword allocated_heap;
45 static mword total_alloc = 0;
46 static mword total_alloc_max = 0;
47
48 static SGEN_TV_DECLARE(last_minor_start);
49 static SGEN_TV_DECLARE(last_major_start);
50
51 /* GC triggers. */
52
53 static gboolean debug_print_allowance = FALSE;
54
55
56 /* use this to tune when to do a major/minor collection */
57 static mword major_collection_trigger_size;
58
59 static mword major_pre_sweep_heap_size;
60 static mword major_start_heap_size;
61
62 static gboolean need_calculate_minor_collection_allowance;
63
64 /* The size of the LOS after the last major collection, after sweeping. */
65 static mword last_collection_los_memory_usage = 0;
66 static mword last_used_slots_size = 0;
67
68 static mword sgen_memgov_available_free_space (void);
69
70
71 /* GC trigger heuristics. */
72
73 static void
74 sgen_memgov_calculate_minor_collection_allowance (void)
75 {
76         size_t new_major, new_heap_size, allowance_target, allowance;
77         size_t decrease;
78
79         if (!need_calculate_minor_collection_allowance)
80                 return;
81
82         SGEN_ASSERT (0, major_collector.have_swept (), "Can only calculate allowance if heap is swept");
83
84         new_major = major_collector.get_bytes_survived_last_sweep ();
85         new_heap_size = new_major + last_collection_los_memory_usage;
86
87         /*
88          * We allow the heap to grow by one third its current size before we start the next
89          * major collection.
90          */
91         allowance_target = new_heap_size * SGEN_DEFAULT_ALLOWANCE_HEAP_SIZE_RATIO;
92
93         allowance = MAX (allowance_target, MIN_MINOR_COLLECTION_ALLOWANCE);
94
95         /*
96          * For the concurrent collector, we decrease the allowance relative to the memory
97          * growth during the M&S phase, survival rate of the collection and the allowance
98          * ratio.
99          */
100         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);
101         if (decrease > allowance)
102                 decrease = allowance;
103         allowance -= decrease;
104
105         if (new_heap_size + allowance > soft_heap_limit) {
106                 if (new_heap_size > soft_heap_limit)
107                         allowance = MIN_MINOR_COLLECTION_ALLOWANCE;
108                 else
109                         allowance = MAX (soft_heap_limit - new_heap_size, MIN_MINOR_COLLECTION_ALLOWANCE);
110         }
111
112         /* FIXME: Why is this here? */
113         if (major_collector.free_swept_blocks)
114                 major_collector.free_swept_blocks (major_collector.get_num_major_sections () * SGEN_DEFAULT_ALLOWANCE_HEAP_SIZE_RATIO);
115
116         major_collection_trigger_size = new_heap_size + allowance;
117
118         need_calculate_minor_collection_allowance = FALSE;
119
120         if (debug_print_allowance) {
121                 SGEN_LOG (0, "Surviving sweep: %ld bytes (%ld major, %ld LOS)", (long)new_heap_size, (long)new_major, (long)last_collection_los_memory_usage);
122                 SGEN_LOG (0, "Allowance: %ld bytes", (long)allowance);
123                 SGEN_LOG (0, "Trigger size: %ld bytes", (long)major_collection_trigger_size);
124         }
125 }
126
127 static inline size_t
128 get_heap_size (void)
129 {
130         return major_collector.get_num_major_sections () * major_collector.section_size + los_memory_usage;
131 }
132
133 gboolean
134 sgen_need_major_collection (mword space_needed)
135 {
136         size_t heap_size;
137
138         if (sgen_concurrent_collection_in_progress ()) {
139                 heap_size = get_heap_size ();
140
141                 if (heap_size <= major_collection_trigger_size)
142                         return FALSE; 
143
144                 /*
145                  * The more the heap grows, the more we need to decrease the allowance above,
146                  * in order to have similar trigger sizes as the synchronous collector.
147                  * If the heap grows so much that we would need to have a negative allowance,
148                  * we force the finishing of the collection, to avoid increased memory usage.
149                  */
150                 if ((heap_size - major_start_heap_size) > major_start_heap_size * SGEN_DEFAULT_ALLOWANCE_HEAP_SIZE_RATIO)
151                         return TRUE;
152                 return FALSE;
153         }
154
155         /* FIXME: This is a cop-out.  We should have some way of figuring this out. */
156         if (!major_collector.have_swept ())
157                 return FALSE;
158
159         if (space_needed > sgen_memgov_available_free_space ())
160                 return TRUE;
161
162         sgen_memgov_calculate_minor_collection_allowance ();
163
164         heap_size = get_heap_size ();
165
166         return heap_size > major_collection_trigger_size;
167 }
168
169 void
170 sgen_memgov_minor_collection_start (void)
171 {
172         total_promoted_size_start = total_promoted_size;
173         SGEN_TV_GETTIME (last_minor_start);
174 }
175
176 static void
177 sgen_add_log_entry (SgenLogEntry *log_entry)
178 {
179         mono_os_mutex_lock (&log_entries_mutex);
180         sgen_pointer_queue_add (&log_entries, log_entry);
181         mono_os_mutex_unlock (&log_entries_mutex);
182 }
183
184 void
185 sgen_memgov_minor_collection_end (const char *reason, gboolean is_overflow)
186 {
187         if (mono_trace_is_traced (G_LOG_LEVEL_INFO, MONO_TRACE_GC)) {
188                 SgenLogEntry *log_entry = (SgenLogEntry*)sgen_alloc_internal (INTERNAL_MEM_LOG_ENTRY);
189                 SGEN_TV_DECLARE (current_time);
190                 SGEN_TV_GETTIME (current_time);
191
192                 log_entry->type = SGEN_LOG_NURSERY;
193                 log_entry->reason = reason;
194                 log_entry->is_overflow = is_overflow;
195                 log_entry->time = SGEN_TV_ELAPSED (last_minor_start, current_time);
196                 log_entry->promoted_size = total_promoted_size - total_promoted_size_start;
197                 log_entry->major_size = major_collector.get_num_major_sections () * major_collector.section_size;
198                 log_entry->major_size_in_use = last_used_slots_size + total_allocated_major - total_allocated_major_end;
199                 log_entry->los_size = los_memory_usage_total;
200                 log_entry->los_size_in_use = los_memory_usage;
201
202                 sgen_add_log_entry (log_entry);
203         }
204 }
205
206 void
207 sgen_memgov_major_pre_sweep (void)
208 {
209         if (sgen_concurrent_collection_in_progress ()) {
210                 major_pre_sweep_heap_size = get_heap_size ();
211         } else {
212                 /* We decrease the allowance only in the concurrent case */
213                 major_pre_sweep_heap_size = major_start_heap_size;
214         }
215 }
216
217 void
218 sgen_memgov_major_post_sweep (mword used_slots_size)
219 {
220         if (mono_trace_is_traced (G_LOG_LEVEL_INFO, MONO_TRACE_GC)) {
221                 SgenLogEntry *log_entry = (SgenLogEntry*)sgen_alloc_internal (INTERNAL_MEM_LOG_ENTRY);
222
223                 log_entry->type = SGEN_LOG_MAJOR_SWEEP_FINISH;
224                 log_entry->major_size = major_collector.get_num_major_sections () * major_collector.section_size;
225                 log_entry->major_size_in_use = used_slots_size + total_allocated_major - total_allocated_major_end;
226
227                 sgen_add_log_entry (log_entry);
228         }
229         last_used_slots_size = used_slots_size;
230 }
231
232 void
233 sgen_memgov_major_collection_start (gboolean concurrent, const char *reason)
234 {
235         need_calculate_minor_collection_allowance = TRUE;
236         major_start_heap_size = get_heap_size ();
237
238         if (debug_print_allowance) {
239                 SGEN_LOG (0, "Starting collection with heap size %ld bytes", (long)major_start_heap_size);
240         }
241         if (concurrent && mono_trace_is_traced (G_LOG_LEVEL_INFO, MONO_TRACE_GC)) {
242                 SgenLogEntry *log_entry = (SgenLogEntry*)sgen_alloc_internal (INTERNAL_MEM_LOG_ENTRY);
243
244                 log_entry->type = SGEN_LOG_MAJOR_CONC_START;
245                 log_entry->reason = reason;
246
247                 sgen_add_log_entry (log_entry);
248         }
249         SGEN_TV_GETTIME (last_major_start);
250 }
251
252 void
253 sgen_memgov_major_collection_end (gboolean forced, gboolean concurrent, const char *reason, gboolean is_overflow)
254 {
255         if (mono_trace_is_traced (G_LOG_LEVEL_INFO, MONO_TRACE_GC)) {
256                 SgenLogEntry *log_entry = (SgenLogEntry*)sgen_alloc_internal (INTERNAL_MEM_LOG_ENTRY);
257                 SGEN_TV_DECLARE (current_time);
258                 SGEN_TV_GETTIME (current_time);
259
260                 if (concurrent) {
261                         log_entry->type = SGEN_LOG_MAJOR_CONC_FINISH;
262                 } else {
263                         log_entry->type = SGEN_LOG_MAJOR_SERIAL;
264                 }
265                 log_entry->time = SGEN_TV_ELAPSED (last_major_start, current_time);
266                 log_entry->reason = reason;
267                 log_entry->is_overflow = is_overflow;
268                 log_entry->los_size = los_memory_usage_total;
269                 log_entry->los_size_in_use = los_memory_usage;
270
271                 sgen_add_log_entry (log_entry);
272         }
273
274         last_collection_los_memory_usage = los_memory_usage;
275         total_allocated_major_end = total_allocated_major;
276         if (forced) {
277                 sgen_get_major_collector ()->finish_sweeping ();
278                 sgen_memgov_calculate_minor_collection_allowance ();
279         }
280 }
281
282 void
283 sgen_memgov_collection_start (int generation)
284 {
285 }
286
287 static void
288 sgen_output_log_entry (SgenLogEntry *entry, gint64 stw_time, int generation)
289 {
290         char full_timing_buff [1024];
291         full_timing_buff [0] = '\0';
292
293         if (!entry->is_overflow)
294                 sprintf (full_timing_buff, "stw %.2fms", stw_time / 10000.0f);
295
296         switch (entry->type) {
297                 case SGEN_LOG_NURSERY:
298                         mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_GC, "GC_MINOR%s: (%s) time %.2fms, %s promoted %zdK major size: %zdK in use: %zdK los size: %zdK in use: %zdK",
299                                 entry->is_overflow ? "_OVERFLOW" : "",
300                                 entry->reason ? entry->reason : "",
301                                 entry->time / 10000.0f,
302                                 (generation == GENERATION_NURSERY) ? full_timing_buff : "",
303                                 entry->promoted_size / 1024,
304                                 entry->major_size / 1024,
305                                 entry->major_size_in_use / 1024,
306                                 entry->los_size / 1024,
307                                 entry->los_size_in_use / 1024);
308                         break;
309                 case SGEN_LOG_MAJOR_SERIAL:
310                         mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_GC, "GC_MAJOR%s: (%s) time %.2fms, %s los size: %zdK in use: %zdK",
311                                 entry->is_overflow ? "_OVERFLOW" : "",
312                                 entry->reason ? entry->reason : "",
313                                 (int)entry->time / 10000.0f,
314                                 full_timing_buff,
315                                 entry->los_size / 1024,
316                                 entry->los_size_in_use / 1024);
317                         break;
318                 case SGEN_LOG_MAJOR_CONC_START:
319                         mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_GC, "GC_MAJOR_CONCURRENT_START: (%s)", entry->reason ? entry->reason : "");
320                         break;
321                 case SGEN_LOG_MAJOR_CONC_FINISH:
322                         mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_GC, "GC_MAJOR_CONCURRENT_FINISH: (%s) time %.2fms, %s los size: %zdK in use: %zdK",
323                                 entry->reason ? entry->reason : "",
324                                 entry->time / 10000.0f,
325                                 full_timing_buff,
326                                 entry->los_size / 1024,
327                                 entry->los_size_in_use / 1024);
328                         break;
329                 case SGEN_LOG_MAJOR_SWEEP_FINISH:
330                         mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_GC, "GC_MAJOR_SWEEP: major size: %zdK in use: %zdK",
331                                 entry->major_size / 1024,
332                                 entry->major_size_in_use / 1024);
333                         break;
334                 default:
335                         SGEN_ASSERT (0, FALSE, "Invalid log entry type");
336                         break;
337         }
338 }
339
340 void
341 sgen_memgov_collection_end (int generation, gint64 stw_time)
342 {
343         /*
344          * At this moment the world has been restarted which means we can log all pending entries
345          * without risking deadlocks.
346          */
347         if (mono_trace_is_traced (G_LOG_LEVEL_INFO, MONO_TRACE_GC)) {
348                 size_t i;
349                 SGEN_ASSERT (0, !sgen_is_world_stopped (), "We can't log if the world is stopped");
350                 mono_os_mutex_lock (&log_entries_mutex);
351                 for (i = 0; i < log_entries.next_slot; i++) {
352                         sgen_output_log_entry (log_entries.data [i], stw_time, generation);
353                         sgen_free_internal (log_entries.data [i], INTERNAL_MEM_LOG_ENTRY);
354                 }
355                 sgen_pointer_queue_clear (&log_entries);
356                 mono_os_mutex_unlock (&log_entries_mutex);
357         }
358 }
359
360 /*
361 Global GC memory tracking.
362 This tracks the total usage of memory by the GC. This includes
363 managed and unmanaged memory.
364 */
365
366 static unsigned long
367 prot_flags_for_activate (int activate)
368 {
369         unsigned long prot_flags = activate? MONO_MMAP_READ|MONO_MMAP_WRITE: MONO_MMAP_NONE;
370         return prot_flags | MONO_MMAP_PRIVATE | MONO_MMAP_ANON;
371 }
372
373 void
374 sgen_assert_memory_alloc (void *ptr, size_t requested_size, const char *assert_description)
375 {
376         if (ptr || !assert_description)
377                 return;
378         fprintf (stderr, "Error: Garbage collector could not allocate %zu bytes of memory for %s.\n", requested_size, assert_description);
379         exit (1);
380 }
381
382 /*
383  * Allocate a big chunk of memory from the OS (usually 64KB to several megabytes).
384  * This must not require any lock.
385  */
386 void*
387 sgen_alloc_os_memory (size_t size, SgenAllocFlags flags, const char *assert_description, MonoMemAccountType type)
388 {
389         void *ptr;
390
391         g_assert (!(flags & ~(SGEN_ALLOC_HEAP | SGEN_ALLOC_ACTIVATE)));
392
393         ptr = mono_valloc (0, size, prot_flags_for_activate (flags & SGEN_ALLOC_ACTIVATE), type);
394         sgen_assert_memory_alloc (ptr, size, assert_description);
395         if (ptr) {
396                 SGEN_ATOMIC_ADD_P (total_alloc, size);
397                 total_alloc_max = MAX (total_alloc_max, total_alloc);
398         }
399         return ptr;
400 }
401
402 /* size must be a power of 2 */
403 // FIXME: remove assert_description
404 void*
405 sgen_alloc_os_memory_aligned (size_t size, mword alignment, SgenAllocFlags flags, const char *assert_description, MonoMemAccountType type)
406 {
407         void *ptr;
408
409         g_assert (!(flags & ~(SGEN_ALLOC_HEAP | SGEN_ALLOC_ACTIVATE)));
410
411         ptr = mono_valloc_aligned (size, alignment, prot_flags_for_activate (flags & SGEN_ALLOC_ACTIVATE), type);
412         sgen_assert_memory_alloc (ptr, size, assert_description);
413         if (ptr) {
414                 SGEN_ATOMIC_ADD_P (total_alloc, size);
415                 total_alloc_max = MAX (total_alloc_max, total_alloc);
416         }
417         return ptr;
418 }
419
420 /*
421  * Free the memory returned by sgen_alloc_os_memory (), returning it to the OS.
422  */
423 void
424 sgen_free_os_memory (void *addr, size_t size, SgenAllocFlags flags, MonoMemAccountType type)
425 {
426         g_assert (!(flags & ~SGEN_ALLOC_HEAP));
427
428         mono_vfree (addr, size, type);
429         SGEN_ATOMIC_ADD_P (total_alloc, -(gssize)size);
430         total_alloc_max = MAX (total_alloc_max, total_alloc);
431 }
432
433 size_t
434 sgen_gc_get_total_heap_allocation (void)
435 {
436         return total_alloc;
437 }
438
439
440 /*
441 Heap Sizing limits.
442 This limit the max size of the heap. It takes into account
443 only memory actively in use to hold heap objects and not
444 for other parts of the GC.
445  */
446 static mword
447 sgen_memgov_available_free_space (void)
448 {
449         return max_heap_size - MIN (allocated_heap, max_heap_size);
450 }
451
452 void
453 sgen_memgov_release_space (mword size, int space)
454 {
455         SGEN_ATOMIC_ADD_P (allocated_heap, -(gssize)size);
456 }
457
458 gboolean
459 sgen_memgov_try_alloc_space (mword size, int space)
460 {
461         if (sgen_memgov_available_free_space () < size) {
462                 SGEN_ASSERT (4, !sgen_workers_is_worker_thread (mono_native_thread_id_get ()), "Memory shouldn't run out in worker thread");
463                 return FALSE;
464         }
465
466         SGEN_ATOMIC_ADD_P (allocated_heap, size);
467         sgen_client_total_allocated_heap_changed (allocated_heap);
468         return TRUE;
469 }
470
471 void
472 sgen_memgov_init (size_t max_heap, size_t soft_limit, gboolean debug_allowance, double allowance_ratio, double save_target)
473 {
474         if (soft_limit)
475                 soft_heap_limit = soft_limit;
476
477         debug_print_allowance = debug_allowance;
478         major_collection_trigger_size = MIN_MINOR_COLLECTION_ALLOWANCE;
479
480         mono_counters_register ("Memgov alloc", MONO_COUNTER_GC | MONO_COUNTER_WORD | MONO_COUNTER_BYTES | MONO_COUNTER_VARIABLE, (void*)&total_alloc);
481         mono_counters_register ("Memgov max alloc", MONO_COUNTER_GC | MONO_COUNTER_WORD | MONO_COUNTER_BYTES | MONO_COUNTER_MONOTONIC, (void*)&total_alloc_max);
482
483         mono_os_mutex_init (&log_entries_mutex);
484
485         sgen_register_fixed_internal_mem_type (INTERNAL_MEM_LOG_ENTRY, sizeof (SgenLogEntry));
486
487         if (max_heap == 0)
488                 return;
489
490         if (max_heap < soft_limit) {
491                 sgen_env_var_error (MONO_GC_PARAMS_NAME, "Setting to minimum.", "`max-heap-size` must be at least as large as `soft-heap-limit`.");
492                 max_heap = soft_limit;
493         }
494
495         if (max_heap < SGEN_DEFAULT_NURSERY_SIZE * 4) {
496                 sgen_env_var_error (MONO_GC_PARAMS_NAME, "Setting to minimum.", "`max-heap-size` must be at least 4 times as large as `nursery size`.");
497                 max_heap = SGEN_DEFAULT_NURSERY_SIZE * 4;
498         }
499         max_heap_size = max_heap - SGEN_DEFAULT_NURSERY_SIZE;
500
501         if (allowance_ratio)
502                 default_allowance_nursery_size_ratio = allowance_ratio;
503
504         if (save_target)
505                 save_target_ratio = save_target;
506 }
507
508 #endif