1use atomic::Ordering;
2
3use crate::global_state::{GlobalState, PauseRequestOutcome};
4use crate::plan::Plan;
5use crate::policy::space::Space;
6use crate::scheduler::GCWorkScheduler;
7use crate::util::constants::BYTES_IN_PAGE;
8use crate::util::conversions;
9use crate::util::options::{GCTriggerSelector, Options, DEFAULT_MAX_NURSERY, DEFAULT_MIN_NURSERY};
10use crate::vm::Collection;
11use crate::vm::VMBinding;
12use crate::MMTK;
13use std::mem::MaybeUninit;
14use std::sync::atomic::AtomicUsize;
15use std::sync::Arc;
16
17pub struct GCTrigger<VM: VMBinding> {
22 plan: MaybeUninit<&'static dyn Plan<VM = VM>>,
25 pub policy: Box<dyn GCTriggerPolicy<VM>>,
27 scheduler: Arc<GCWorkScheduler<VM>>,
28 options: Arc<Options>,
29 state: Arc<GlobalState>,
30}
31
32impl<VM: VMBinding> GCTrigger<VM> {
33 pub fn new(
34 options: Arc<Options>,
35 scheduler: Arc<GCWorkScheduler<VM>>,
36 state: Arc<GlobalState>,
37 ) -> Self {
38 GCTrigger {
39 plan: MaybeUninit::uninit(),
40 policy: match *options.gc_trigger {
41 GCTriggerSelector::FixedHeapSize(size) => Box::new(FixedHeapSizeTrigger {
42 total_pages: conversions::bytes_to_pages_up(size),
43 }),
44 GCTriggerSelector::DynamicHeapSize(min, max) => 'dynamic_heap_size: {
45 let min_pages = conversions::bytes_to_pages_up(min);
46 let max_pages = conversions::bytes_to_pages_up(max);
47
48 if *options.plan == crate::util::options::PlanSelector::NoGC {
49 warn!("Cannot use dynamic heap size with NoGC. Using fixed heap size trigger instead.");
50 break 'dynamic_heap_size Box::new(FixedHeapSizeTrigger {
51 total_pages: max_pages,
52 });
53 }
54
55 Box::new(MemBalancerTrigger::new(min_pages, max_pages))
56 }
57 GCTriggerSelector::Delegated => {
58 <VM::VMCollection as crate::vm::Collection<VM>>::create_gc_trigger()
59 }
60 },
61 options,
62 scheduler,
63 state,
64 }
65 }
66
67 pub fn set_plan(&mut self, plan: &'static dyn Plan<VM = VM>) {
69 self.plan.write(plan);
70 }
71
72 fn plan(&self) -> &dyn Plan<VM = VM> {
73 unsafe { self.plan.assume_init() }
74 }
75
76 fn request(&self) -> bool {
80 match self.state.gc_status.try_request_pause() {
86 PauseRequestOutcome::Uninitialized => panic!(
90 "GC is not allowed here: collection is not initialized (did you call initialize_collection()?)."
91 ),
92 PauseRequestOutcome::AlreadyRequested => true,
93 PauseRequestOutcome::Requested => {
94 probe!(mmtk, gc_requested);
95 self.scheduler.request_schedule_collection();
96 true
97 }
98 }
99 }
100
101 pub fn poll(&self, space_full: bool, space: Option<&dyn Space<VM>>) -> bool {
109 if !VM::VMCollection::is_collection_enabled() {
110 return false;
111 }
112
113 let plan = self.plan();
114 if self
115 .policy
116 .is_gc_required(space_full, space.map(|s| SpaceStats::new(s)), plan)
117 {
118 info!(
119 "[POLL] {}{} ({}/{} pages)",
120 if let Some(space) = space {
121 format!("{}: ", space.get_name())
122 } else {
123 "".to_string()
124 },
125 "Triggering collection",
126 plan.get_reserved_pages(),
127 plan.get_total_pages(),
128 );
129 return self.request();
130 }
131 false
132 }
133
134 pub fn handle_user_collection_request(&self, force: bool, exhaustive: bool) -> bool {
141 if !self.plan().constraints().collects_garbage {
142 warn!("User attempted a collection request, but the plan can not do GC. The request is ignored.");
143 return false;
144 }
145
146 if force || !*self.options.ignore_system_gc && VM::VMCollection::is_collection_enabled() {
147 info!("User triggering collection");
148 if exhaustive {
150 if let Some(gen) = self.plan().generational() {
151 gen.force_full_heap_collection();
152 }
153 }
154
155 self.state
156 .user_triggered_collection
157 .store(true, Ordering::Relaxed);
158 return self.request();
159 }
160
161 false
162 }
163
164 #[allow(unused)]
169 pub fn trigger_internal_collection_request(&self) {
170 self.state
171 .last_internal_triggered_collection
172 .store(true, Ordering::Relaxed);
173 self.state
174 .internal_triggered_collection
175 .store(true, Ordering::Relaxed);
176 self.request();
179 unimplemented!()
181 }
182
183 pub fn should_do_stress_gc(&self) -> bool {
184 Self::should_do_stress_gc_inner(&self.state, &self.options)
185 }
186
187 pub(crate) fn should_do_stress_gc_inner(state: &GlobalState, options: &Options) -> bool {
190 state.is_initialized()
191 && (state.allocation_bytes.load(Ordering::SeqCst) > *options.stress_factor)
192 }
193
194 pub fn is_heap_full(&self) -> bool {
196 self.policy.is_heap_full(self.plan())
197 }
198
199 pub fn get_max_nursery_bytes(&self) -> usize {
201 use crate::util::options::NurserySize;
202 debug_assert!(self.plan().generational().is_some());
203 match *self.options.nursery {
204 NurserySize::Bounded { min: _, max } => max,
205 NurserySize::ProportionalBounded { min: _, max } => {
206 let heap_size_bytes =
207 conversions::pages_to_bytes(self.policy.get_current_heap_size_in_pages());
208 let max_bytes = heap_size_bytes as f64 * max;
209 let max_bytes = conversions::raw_align_up(max_bytes as usize, BYTES_IN_PAGE);
210 if max_bytes > DEFAULT_MAX_NURSERY {
211 warn!("Proportional nursery with max size {} ({}) is larger than DEFAULT_MAX_NURSERY ({}). Use DEFAULT_MAX_NURSERY instead.", max, max_bytes, DEFAULT_MAX_NURSERY);
212 DEFAULT_MAX_NURSERY
213 } else {
214 max_bytes
215 }
216 }
217 NurserySize::Fixed(sz) => sz,
218 }
219 }
220
221 pub fn get_min_nursery_bytes(&self) -> usize {
223 use crate::util::options::NurserySize;
224 debug_assert!(self.plan().generational().is_some());
225 match *self.options.nursery {
226 NurserySize::Bounded { min, max: _ } => min,
227 NurserySize::ProportionalBounded { min, max: _ } => {
228 let min_bytes =
229 conversions::pages_to_bytes(self.policy.get_current_heap_size_in_pages())
230 as f64
231 * min;
232 let min_bytes = conversions::raw_align_up(min_bytes as usize, BYTES_IN_PAGE);
233 if min_bytes < DEFAULT_MIN_NURSERY {
234 warn!("Proportional nursery with min size {} ({}) is smaller than DEFAULT_MIN_NURSERY ({}). Use DEFAULT_MIN_NURSERY instead.", min, min_bytes, DEFAULT_MIN_NURSERY);
235 DEFAULT_MIN_NURSERY
236 } else {
237 min_bytes
238 }
239 }
240 NurserySize::Fixed(sz) => sz,
241 }
242 }
243
244 pub fn get_max_nursery_pages(&self) -> usize {
246 crate::util::conversions::bytes_to_pages_up(self.get_max_nursery_bytes())
247 }
248
249 pub fn get_min_nursery_pages(&self) -> usize {
251 crate::util::conversions::bytes_to_pages_up(self.get_min_nursery_bytes())
252 }
253
254 pub fn will_oom_on_alloc(&self, size: usize) -> bool {
270 let max_pages = self.policy.get_max_heap_size_in_pages();
271 let requested_pages = size >> crate::util::constants::LOG_BYTES_IN_PAGE;
272 requested_pages > max_pages
273 }
274}
275
276pub struct SpaceStats<'a, VM: VMBinding>(pub(crate) &'a dyn Space<VM>);
280
281impl<'a, VM: VMBinding> SpaceStats<'a, VM> {
282 fn new(space: &'a dyn Space<VM>) -> Self {
284 Self(space)
285 }
286
287 pub fn reserved_pages(&self) -> usize {
289 self.0.reserved_pages()
290 }
291
292 }
295
296pub trait GCTriggerPolicy<VM: VMBinding>: Sync + Send {
300 fn on_pending_allocation(&self, _pages: usize) {}
305 fn on_gc_start(&self, _mmtk: &'static MMTK<VM>) {}
307 fn on_gc_release(&self, _mmtk: &'static MMTK<VM>) {}
312 fn on_gc_end(&self, _mmtk: &'static MMTK<VM>) {}
314 fn is_gc_required(
324 &self,
325 space_full: bool,
326 space: Option<SpaceStats<VM>>,
327 plan: &dyn Plan<VM = VM>,
328 ) -> bool;
329 fn is_heap_full(&self, plan: &dyn Plan<VM = VM>) -> bool;
331 fn get_current_heap_size_in_pages(&self) -> usize;
333 fn get_max_heap_size_in_pages(&self) -> usize;
335 fn can_heap_size_grow(&self) -> bool;
337}
338
339pub struct FixedHeapSizeTrigger {
341 total_pages: usize,
342}
343impl<VM: VMBinding> GCTriggerPolicy<VM> for FixedHeapSizeTrigger {
344 fn is_gc_required(
345 &self,
346 space_full: bool,
347 space: Option<SpaceStats<VM>>,
348 plan: &dyn Plan<VM = VM>,
349 ) -> bool {
350 plan.collection_required(space_full, space)
352 }
353
354 fn is_heap_full(&self, plan: &dyn Plan<VM = VM>) -> bool {
355 plan.get_reserved_pages() > self.total_pages
357 }
358
359 fn get_current_heap_size_in_pages(&self) -> usize {
360 self.total_pages
361 }
362
363 fn get_max_heap_size_in_pages(&self) -> usize {
364 self.total_pages
365 }
366
367 fn can_heap_size_grow(&self) -> bool {
368 false
369 }
370}
371
372use atomic_refcell::AtomicRefCell;
373use std::time::Instant;
374
375pub struct MemBalancerTrigger {
381 min_heap_pages: usize,
383 max_heap_pages: usize,
385 current_heap_pages: AtomicUsize,
387 pending_pages: AtomicUsize,
390 stats: AtomicRefCell<MemBalancerStats>,
392}
393
394#[derive(Copy, Clone, Debug)]
395struct MemBalancerStats {
396 allocation_pages_prev: Option<f64>,
399 allocation_time_prev: Option<f64>,
401 collection_pages_prev: Option<f64>,
403 collection_time_prev: Option<f64>,
405
406 allocation_pages: f64,
409 allocation_time: f64,
411 collection_pages: f64,
413 collection_time: f64,
415
416 gc_start_time: Instant,
418 gc_end_time: Instant,
420
421 gc_release_live_pages: usize,
423 gc_end_live_pages: usize,
425}
426
427impl std::default::Default for MemBalancerStats {
428 fn default() -> Self {
429 let now = Instant::now();
430 Self {
431 allocation_pages_prev: None,
432 allocation_time_prev: None,
433 collection_pages_prev: None,
434 collection_time_prev: None,
435 allocation_pages: 0f64,
436 allocation_time: 0f64,
437 collection_pages: 0f64,
438 collection_time: 0f64,
439 gc_start_time: now,
440 gc_end_time: now,
441 gc_release_live_pages: 0,
442 gc_end_live_pages: 0,
443 }
444 }
445}
446
447use crate::plan::GenerationalPlan;
448
449impl MemBalancerStats {
450 fn generational_mem_stats_on_gc_start<VM: VMBinding>(
456 &mut self,
457 _plan: &dyn GenerationalPlan<VM = VM>,
458 ) {
459 }
461 fn generational_mem_stats_on_gc_release<VM: VMBinding>(
462 &mut self,
463 plan: &dyn GenerationalPlan<VM = VM>,
464 ) {
465 if !plan.is_current_gc_nursery() {
466 self.gc_release_live_pages = plan.get_mature_reserved_pages();
467
468 let promoted = self
470 .gc_release_live_pages
471 .saturating_sub(self.gc_end_live_pages);
472 self.allocation_pages = promoted as f64;
473 trace!(
474 "promoted = mature live before release {} - mature live at prev gc end {} = {}",
475 self.gc_release_live_pages,
476 self.gc_end_live_pages,
477 promoted
478 );
479 trace!(
480 "allocated pages (accumulated to) = {}",
481 self.allocation_pages
482 );
483 }
484 }
485 fn generational_mem_stats_on_gc_end<VM: VMBinding>(
487 &mut self,
488 plan: &dyn GenerationalPlan<VM = VM>,
489 ) -> bool {
490 if !plan.is_current_gc_nursery() {
491 self.gc_end_live_pages = plan.get_mature_reserved_pages();
492 self.collection_pages = self.gc_end_live_pages as f64;
494 trace!(
495 "collected pages = mature live at gc end {} - mature live at gc release {} = {}",
496 self.gc_release_live_pages,
497 self.gc_end_live_pages,
498 self.collection_pages
499 );
500 true
501 } else {
502 false
503 }
504 }
505
506 fn non_generational_mem_stats_on_gc_start<VM: VMBinding>(&mut self, mmtk: &'static MMTK<VM>) {
511 self.allocation_pages = mmtk
512 .get_plan()
513 .get_reserved_pages()
514 .saturating_sub(self.gc_end_live_pages) as f64;
515 trace!(
516 "allocated pages = used {} - live in last gc {} = {}",
517 mmtk.get_plan().get_reserved_pages(),
518 self.gc_end_live_pages,
519 self.allocation_pages
520 );
521 }
522 fn non_generational_mem_stats_on_gc_release<VM: VMBinding>(&mut self, mmtk: &'static MMTK<VM>) {
523 self.gc_release_live_pages = mmtk.get_plan().get_reserved_pages();
524 trace!("live before release = {}", self.gc_release_live_pages);
525 }
526 fn non_generational_mem_stats_on_gc_end<VM: VMBinding>(&mut self, mmtk: &'static MMTK<VM>) {
527 self.gc_end_live_pages = mmtk.get_plan().get_reserved_pages();
528 trace!("live pages = {}", self.gc_end_live_pages);
529 self.collection_pages = self.gc_end_live_pages as f64;
531 trace!(
532 "collected pages = live at gc end {} - live at gc release {} = {}",
533 self.gc_release_live_pages,
534 self.gc_end_live_pages,
535 self.collection_pages
536 );
537 }
538}
539
540impl<VM: VMBinding> GCTriggerPolicy<VM> for MemBalancerTrigger {
541 fn is_gc_required(
542 &self,
543 space_full: bool,
544 space: Option<SpaceStats<VM>>,
545 plan: &dyn Plan<VM = VM>,
546 ) -> bool {
547 plan.collection_required(space_full, space)
549 }
550
551 fn on_pending_allocation(&self, pages: usize) {
552 self.pending_pages.fetch_add(pages, Ordering::SeqCst);
553 }
554
555 fn on_gc_start(&self, mmtk: &'static MMTK<VM>) {
556 trace!("=== on_gc_start ===");
557 self.access_stats(|stats| {
558 stats.gc_start_time = Instant::now();
559 stats.allocation_time += (stats.gc_start_time - stats.gc_end_time).as_secs_f64();
560 trace!(
561 "gc_start = {:?}, allocation_time = {}",
562 stats.gc_start_time,
563 stats.allocation_time
564 );
565
566 if let Some(plan) = mmtk.get_plan().generational() {
567 stats.generational_mem_stats_on_gc_start(plan);
568 } else {
569 stats.non_generational_mem_stats_on_gc_start(mmtk);
570 }
571 });
572 }
573
574 fn on_gc_release(&self, mmtk: &'static MMTK<VM>) {
575 trace!("=== on_gc_release ===");
576 self.access_stats(|stats| {
577 if let Some(plan) = mmtk.get_plan().generational() {
578 stats.generational_mem_stats_on_gc_release(plan);
579 } else {
580 stats.non_generational_mem_stats_on_gc_release(mmtk);
581 }
582 });
583 }
584
585 fn on_gc_end(&self, mmtk: &'static MMTK<VM>) {
586 trace!("=== on_gc_end ===");
587 self.access_stats(|stats| {
588 stats.gc_end_time = Instant::now();
589 stats.collection_time += (stats.gc_end_time - stats.gc_start_time).as_secs_f64();
590 trace!(
591 "gc_end = {:?}, collection_time = {}",
592 stats.gc_end_time,
593 stats.collection_time
594 );
595
596 if let Some(plan) = mmtk.get_plan().generational() {
597 if stats.generational_mem_stats_on_gc_end(plan) {
598 self.compute_new_heap_limit(
599 mmtk.get_plan().get_reserved_pages(),
600 mmtk.get_plan().get_collection_reserved_pages()
603 + mmtk.gc_trigger.get_min_nursery_pages(),
604 stats,
605 );
606 }
607 } else {
608 stats.non_generational_mem_stats_on_gc_end(mmtk);
609 self.compute_new_heap_limit(
610 mmtk.get_plan().get_reserved_pages(),
611 mmtk.get_plan().get_collection_reserved_pages(),
612 stats,
613 );
614 }
615 });
616 self.pending_pages.store(0, Ordering::SeqCst);
618 }
619
620 fn is_heap_full(&self, plan: &dyn Plan<VM = VM>) -> bool {
621 plan.get_reserved_pages() > self.current_heap_pages.load(Ordering::Relaxed)
623 }
624
625 fn get_current_heap_size_in_pages(&self) -> usize {
626 self.current_heap_pages.load(Ordering::Relaxed)
627 }
628
629 fn get_max_heap_size_in_pages(&self) -> usize {
630 self.max_heap_pages
631 }
632
633 fn can_heap_size_grow(&self) -> bool {
634 self.current_heap_pages.load(Ordering::Relaxed) < self.max_heap_pages
635 }
636}
637impl MemBalancerTrigger {
638 fn new(min_heap_pages: usize, max_heap_pages: usize) -> Self {
639 Self {
640 min_heap_pages,
641 max_heap_pages,
642 pending_pages: AtomicUsize::new(0),
643 current_heap_pages: AtomicUsize::new(min_heap_pages),
645 stats: AtomicRefCell::new(Default::default()),
646 }
647 }
648
649 fn access_stats<F>(&self, mut f: F)
650 where
651 F: FnMut(&mut MemBalancerStats),
652 {
653 let mut stats = self.stats.borrow_mut();
654 f(&mut stats);
655 }
656
657 fn compute_new_heap_limit(
658 &self,
659 live: usize,
660 extra_reserve: usize,
661 stats: &mut MemBalancerStats,
662 ) {
663 trace!("compute new heap limit: {:?}", stats);
664
665 const ALLOCATION_SMOOTH_FACTOR: f64 = 0.95;
667 const COLLECTION_SMOOTH_FACTOR: f64 = 0.5;
668 const TUNING_FACTOR: f64 = 0.2;
669
670 let smooth = |prev: Option<f64>, cur, factor| {
672 prev.map(|p| p * factor + cur * (1.0f64 - factor))
673 .unwrap_or(cur)
674 };
675 let alloc_mem = smooth(
676 stats.allocation_pages_prev,
677 stats.allocation_pages,
678 ALLOCATION_SMOOTH_FACTOR,
679 );
680 let alloc_time = smooth(
681 stats.allocation_time_prev,
682 stats.allocation_time,
683 ALLOCATION_SMOOTH_FACTOR,
684 );
685 let gc_mem = smooth(
686 stats.collection_pages_prev,
687 stats.collection_pages,
688 COLLECTION_SMOOTH_FACTOR,
689 );
690 let gc_time = smooth(
691 stats.collection_time_prev,
692 stats.collection_time,
693 COLLECTION_SMOOTH_FACTOR,
694 );
695 trace!(
696 "after smoothing, alloc mem = {}, alloc_time = {}",
697 alloc_mem,
698 alloc_time
699 );
700 trace!(
701 "after smoothing, gc mem = {}, gc_time = {}",
702 gc_mem,
703 gc_time
704 );
705
706 stats.allocation_pages_prev = Some(stats.allocation_pages);
708 stats.allocation_pages = 0f64;
709 stats.allocation_time_prev = Some(stats.allocation_time);
710 stats.allocation_time = 0f64;
711 stats.collection_pages_prev = Some(stats.collection_pages);
712 stats.collection_pages = 0f64;
713 stats.collection_time_prev = Some(stats.collection_time);
714 stats.collection_time = 0f64;
715
716 let e: f64 = if alloc_mem != 0f64 && gc_mem != 0f64 && alloc_time != 0f64 && gc_time != 0f64
718 {
719 let mut e = live as f64;
720 e *= alloc_mem / alloc_time;
721 e /= TUNING_FACTOR;
722 e /= gc_mem / gc_time;
723 e.sqrt()
724 } else {
725 (live as f64 * 4096f64).sqrt()
727 };
728
729 let pending_pages = self.pending_pages.load(Ordering::SeqCst);
731
732 let optimal_heap = live + e as usize + extra_reserve + pending_pages;
734 trace!(
735 "optimal = live {} + sqrt(live) {} + extra {}",
736 live,
737 e,
738 extra_reserve
739 );
740
741 let new_heap = optimal_heap.clamp(self.min_heap_pages, self.max_heap_pages);
743 debug!(
744 "MemBalander: new heap limit = {} pages (optimal = {}, clamped to [{}, {}])",
745 new_heap, optimal_heap, self.min_heap_pages, self.max_heap_pages
746 );
747 self.current_heap_pages.store(new_heap, Ordering::Relaxed);
748 }
749}