Skip to content

Commit 541020b

Browse files
authored
Implement mem balancer (#724)
This PR implements mem balancer (https://dl.acm.org/doi/pdf/10.1145/3563323). Changes: * Introduce a trait `GenerationalPlan: Plan`, and move methods that are specific to generational plans to the new trait from the old `trait Plan`. * Change the return type of `Plan::generational()` to return an option of `&dyn GenerationalPlan`. We can use this to know whether a plan is generational or not. If it is, we can further invoke methods about the generational plan. * Add `GCTriggerPolicy::on_gc_release()` which is called in the `Release` work packet. A `GCTriggerPolicy` can use this to get stats before we reclaim memory (e.g. to get promoted size). * Add an implementation of mem balancer to replace the current simple resize policy. * Add some logging to help debug GC triggering.
1 parent 78f0851 commit 541020b

File tree

12 files changed

+463
-103
lines changed

12 files changed

+463
-103
lines changed

src/plan/generational/barrier.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,21 @@ use crate::MMTK;
1313
use super::gc_work::GenNurseryProcessEdges;
1414
use super::gc_work::ProcessModBuf;
1515
use super::gc_work::ProcessRegionModBuf;
16-
use super::global::Gen;
16+
use super::global::CommonGenPlan;
1717

1818
pub struct GenObjectBarrierSemantics<VM: VMBinding> {
1919
/// MMTk instance
2020
mmtk: &'static MMTK<VM>,
2121
/// Generational plan
22-
gen: &'static Gen<VM>,
22+
gen: &'static CommonGenPlan<VM>,
2323
/// Object modbuf. Contains a list of objects that may contain pointers to the nursery space.
2424
modbuf: VectorQueue<ObjectReference>,
2525
/// Array-copy modbuf. Contains a list of sub-arrays or array slices that may contain pointers to the nursery space.
2626
region_modbuf: VectorQueue<VM::VMMemorySlice>,
2727
}
2828

2929
impl<VM: VMBinding> GenObjectBarrierSemantics<VM> {
30-
pub fn new(mmtk: &'static MMTK<VM>, gen: &'static Gen<VM>) -> Self {
30+
pub fn new(mmtk: &'static MMTK<VM>, gen: &'static CommonGenPlan<VM>) -> Self {
3131
Self {
3232
mmtk,
3333
gen,

src/plan/generational/copying/global.rs

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use super::gc_work::GenCopyGCWorkContext;
22
use super::gc_work::GenCopyNurseryGCWorkContext;
33
use super::mutator::ALLOCATOR_MAPPING;
4-
use crate::plan::generational::global::Gen;
4+
use crate::plan::generational::global::CommonGenPlan;
5+
use crate::plan::generational::global::GenerationalPlan;
56
use crate::plan::global::BasePlan;
67
use crate::plan::global::CommonPlan;
78
use crate::plan::global::CreateGeneralPlanArgs;
@@ -27,7 +28,7 @@ use mmtk_macros::PlanTraceObject;
2728
#[derive(PlanTraceObject)]
2829
pub struct GenCopy<VM: VMBinding> {
2930
#[fallback_trace]
30-
pub gen: Gen<VM>,
31+
pub gen: CommonGenPlan<VM>,
3132
pub hi: AtomicBool,
3233
#[trace(CopySemantics::Mature)]
3334
pub copyspace0: CopySpace<VM>,
@@ -98,7 +99,7 @@ impl<VM: VMBinding> Plan for GenCopy<VM> {
9899
}
99100

100101
fn prepare(&mut self, tls: VMWorkerThread) {
101-
let full_heap = !self.is_current_gc_nursery();
102+
let full_heap = !self.gen.is_current_gc_nursery();
102103
self.gen.prepare(tls);
103104
if full_heap {
104105
self.hi
@@ -118,7 +119,7 @@ impl<VM: VMBinding> Plan for GenCopy<VM> {
118119
}
119120

120121
fn release(&mut self, tls: VMWorkerThread) {
121-
let full_heap = !self.is_current_gc_nursery();
122+
let full_heap = !self.gen.is_current_gc_nursery();
122123
self.gen.release(tls);
123124
if full_heap {
124125
self.fromspace().release();
@@ -127,7 +128,7 @@ impl<VM: VMBinding> Plan for GenCopy<VM> {
127128

128129
fn end_of_gc(&mut self, _tls: VMWorkerThread) {
129130
self.gen
130-
.set_next_gc_full_heap(Gen::should_next_gc_be_full_heap(self));
131+
.set_next_gc_full_heap(CommonGenPlan::should_next_gc_be_full_heap(self));
131132
}
132133

133134
fn get_collection_reserved_pages(&self) -> usize {
@@ -147,10 +148,6 @@ impl<VM: VMBinding> Plan for GenCopy<VM> {
147148
>> 1
148149
}
149150

150-
fn get_mature_physical_pages_available(&self) -> usize {
151-
self.tospace().available_physical_pages()
152-
}
153-
154151
fn base(&self) -> &BasePlan<VM> {
155152
&self.gen.common.base
156153
}
@@ -159,12 +156,22 @@ impl<VM: VMBinding> Plan for GenCopy<VM> {
159156
&self.gen.common
160157
}
161158

162-
fn generational(&self) -> &Gen<VM> {
159+
fn generational(&self) -> Option<&dyn GenerationalPlan<VM = Self::VM>> {
160+
Some(self)
161+
}
162+
}
163+
164+
impl<VM: VMBinding> GenerationalPlan for GenCopy<VM> {
165+
fn common_gen(&self) -> &CommonGenPlan<Self::VM> {
163166
&self.gen
164167
}
165168

166-
fn is_current_gc_nursery(&self) -> bool {
167-
!self.gen.gc_full_heap.load(Ordering::SeqCst)
169+
fn get_mature_physical_pages_available(&self) -> usize {
170+
self.tospace().available_physical_pages()
171+
}
172+
173+
fn get_mature_reserved_pages(&self) -> usize {
174+
self.tospace().reserved_pages()
168175
}
169176
}
170177

@@ -187,7 +194,7 @@ impl<VM: VMBinding> GenCopy<VM> {
187194
);
188195

189196
let res = GenCopy {
190-
gen: Gen::new(plan_args),
197+
gen: CommonGenPlan::new(plan_args),
191198
hi: AtomicBool::new(false),
192199
copyspace0,
193200
copyspace1,

src/plan/generational/gc_work.rs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use atomic::Ordering;
22

3-
use crate::plan::generational::global::Gen;
3+
use crate::plan::generational::global::CommonGenPlan;
44
use crate::policy::space::Space;
55
use crate::scheduler::{gc_work::*, GCWork, GCWorker};
66
use crate::util::ObjectReference;
@@ -14,7 +14,7 @@ use std::ops::{Deref, DerefMut};
1414
/// [`crate::scheduler::gc_work::SFTProcessEdges`]. If a plan uses `SFTProcessEdges`,
1515
/// it does not need to use this type.
1616
pub struct GenNurseryProcessEdges<VM: VMBinding> {
17-
gen: &'static Gen<VM>,
17+
gen: &'static CommonGenPlan<VM>,
1818
base: ProcessEdgesBase<VM>,
1919
}
2020

@@ -24,7 +24,7 @@ impl<VM: VMBinding> ProcessEdgesWork for GenNurseryProcessEdges<VM> {
2424

2525
fn new(edges: Vec<EdgeOf<Self>>, roots: bool, mmtk: &'static MMTK<VM>) -> Self {
2626
let base = ProcessEdgesBase::new(edges, roots, mmtk);
27-
let gen = base.plan().generational();
27+
let gen = base.plan().generational().unwrap().common_gen();
2828
Self { gen, base }
2929
}
3030
#[inline]
@@ -95,7 +95,13 @@ impl<E: ProcessEdgesWork> GCWork<E::VM> for ProcessModBuf<E> {
9595
);
9696
}
9797
// scan modbuf only if the current GC is a nursery GC
98-
if mmtk.plan.is_current_gc_nursery() {
98+
if mmtk
99+
.plan
100+
.generational()
101+
.unwrap()
102+
.common_gen()
103+
.is_current_gc_nursery()
104+
{
99105
// Scan objects in the modbuf and forward pointers
100106
let modbuf = std::mem::take(&mut self.modbuf);
101107
GCWork::do_work(
@@ -129,7 +135,13 @@ impl<E: ProcessEdgesWork> GCWork<E::VM> for ProcessRegionModBuf<E> {
129135
#[inline(always)]
130136
fn do_work(&mut self, worker: &mut GCWorker<E::VM>, mmtk: &'static MMTK<E::VM>) {
131137
// Scan modbuf only if the current GC is a nursery GC
132-
if mmtk.plan.is_current_gc_nursery() {
138+
if mmtk
139+
.plan
140+
.generational()
141+
.unwrap()
142+
.common_gen()
143+
.is_current_gc_nursery()
144+
{
133145
// Collect all the entries in all the slices
134146
let mut edges = vec![];
135147
for slice in &self.modbuf {

src/plan/generational/global.rs

Lines changed: 58 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ use crate::plan::Plan;
55
use crate::policy::copyspace::CopySpace;
66
use crate::policy::space::Space;
77
use crate::scheduler::*;
8-
use crate::util::conversions;
98
use crate::util::copy::CopySemantics;
109
use crate::util::heap::VMRequest;
1110
use crate::util::metadata::side_metadata::SideMetadataSanity;
@@ -22,7 +21,7 @@ use mmtk_macros::PlanTraceObject;
2221
/// Common implementation for generational plans. Each generational plan
2322
/// should include this type, and forward calls to it where possible.
2423
#[derive(PlanTraceObject)]
25-
pub struct Gen<VM: VMBinding> {
24+
pub struct CommonGenPlan<VM: VMBinding> {
2625
/// The nursery space.
2726
#[trace(CopySemantics::PromoteToMature)]
2827
pub nursery: CopySpace<VM>,
@@ -36,21 +35,21 @@ pub struct Gen<VM: VMBinding> {
3635
pub full_heap_gc_count: Arc<Mutex<EventCounter>>,
3736
}
3837

39-
impl<VM: VMBinding> Gen<VM> {
38+
impl<VM: VMBinding> CommonGenPlan<VM> {
4039
pub fn new(mut args: CreateSpecificPlanArgs<VM>) -> Self {
4140
let nursery = CopySpace::new(
4241
args.get_space_args(
4342
"nursery",
4443
true,
45-
VMRequest::fixed_extent(args.global_args.options.get_max_nursery(), false),
44+
VMRequest::fixed_extent(args.global_args.options.get_max_nursery_bytes(), false),
4645
),
4746
true,
4847
);
4948
let common = CommonPlan::new(args);
5049

5150
let full_heap_gc_count = common.base.stats.new_event_counter("majorGC", true, true);
5251

53-
Gen {
52+
CommonGenPlan {
5453
nursery,
5554
common,
5655
gc_full_heap: AtomicBool::default(),
@@ -97,28 +96,35 @@ impl<VM: VMBinding> Gen<VM> {
9796
///
9897
/// Returns `true` if the nursery has grown to the extent that it may not be able to be copied
9998
/// into the mature space.
100-
fn virtual_memory_exhausted<P: Plan>(&self, plan: &P) -> bool {
99+
fn virtual_memory_exhausted(plan: &dyn GenerationalPlan<VM = VM>) -> bool {
101100
((plan.get_collection_reserved_pages() as f64
102101
* VM::VMObjectModel::VM_WORST_CASE_COPY_EXPANSION) as usize)
103102
> plan.get_mature_physical_pages_available()
104103
}
105104

106105
/// Check if we need a GC based on the nursery space usage. This method may mark
107106
/// the following GC as a full heap GC.
108-
pub fn collection_required<P: Plan>(
107+
pub fn collection_required<P: Plan<VM = VM>>(
109108
&self,
110109
plan: &P,
111110
space_full: bool,
112111
space: Option<&dyn Space<VM>>,
113112
) -> bool {
114-
let nursery_full = self.nursery.reserved_pages()
115-
>= (conversions::bytes_to_pages_up(self.common.base.options.get_max_nursery()));
113+
let cur_nursery = self.nursery.reserved_pages();
114+
let max_nursery = self.common.base.options.get_max_nursery_pages();
115+
let nursery_full = cur_nursery >= max_nursery;
116+
trace!(
117+
"nursery_full = {:?} (nursery = {}, max_nursery = {})",
118+
nursery_full,
119+
cur_nursery,
120+
max_nursery,
121+
);
116122

117123
if nursery_full {
118124
return true;
119125
}
120126

121-
if self.virtual_memory_exhausted(plan) {
127+
if Self::virtual_memory_exhausted(plan.generational().unwrap()) {
122128
return true;
123129
}
124130

@@ -146,11 +152,12 @@ impl<VM: VMBinding> Gen<VM> {
146152

147153
/// Check if we should do a full heap GC. It returns true if we should have a full heap GC.
148154
/// It also sets gc_full_heap based on the result.
149-
pub fn requires_full_heap_collection<P: Plan>(&self, plan: &P) -> bool {
155+
pub fn requires_full_heap_collection<P: Plan<VM = VM>>(&self, plan: &P) -> bool {
150156
// Allow the same 'true' block for if-else.
151157
// The conditions are complex, and it is easier to read if we put them to separate if blocks.
152158
#[allow(clippy::if_same_then_else, clippy::needless_bool)]
153159
let is_full_heap = if crate::plan::generational::FULL_NURSERY_GC {
160+
trace!("full heap: forced full heap");
154161
// For barrier overhead measurements, we always do full gc in nursery collections.
155162
true
156163
} else if self
@@ -160,6 +167,7 @@ impl<VM: VMBinding> Gen<VM> {
160167
.load(Ordering::SeqCst)
161168
&& *self.common.base.options.full_heap_system_gc
162169
{
170+
trace!("full heap: user triggered");
163171
// User triggered collection, and we force full heap for user triggered collection
164172
true
165173
} else if self.next_gc_full_heap.load(Ordering::SeqCst)
@@ -170,9 +178,18 @@ impl<VM: VMBinding> Gen<VM> {
170178
.load(Ordering::SeqCst)
171179
> 1
172180
{
181+
trace!(
182+
"full heap: next_gc_full_heap = {}, cur_collection_attempts = {}",
183+
self.next_gc_full_heap.load(Ordering::SeqCst),
184+
self.common
185+
.base
186+
.cur_collection_attempts
187+
.load(Ordering::SeqCst)
188+
);
173189
// Forces full heap collection
174190
true
175-
} else if self.virtual_memory_exhausted(plan) {
191+
} else if Self::virtual_memory_exhausted(plan.generational().unwrap()) {
192+
trace!("full heap: virtual memory exhausted");
176193
true
177194
} else {
178195
// We use an Appel-style nursery. The default GC (even for a "heap-full" collection)
@@ -250,8 +267,16 @@ impl<VM: VMBinding> Gen<VM> {
250267
/// [`get_available_pages`](crate::plan::Plan::get_available_pages)
251268
/// whose value depends on which spaces have been released.
252269
pub fn should_next_gc_be_full_heap(plan: &dyn Plan<VM = VM>) -> bool {
253-
plan.get_available_pages()
254-
< conversions::bytes_to_pages_up(plan.base().options.get_min_nursery())
270+
let available = plan.get_available_pages();
271+
let min_nursery = plan.base().options.get_min_nursery_pages();
272+
let next_gc_full_heap = available < min_nursery;
273+
trace!(
274+
"next gc will be full heap? {}, availabe pages = {}, min nursery = {}",
275+
next_gc_full_heap,
276+
available,
277+
min_nursery
278+
);
279+
next_gc_full_heap
255280
}
256281

257282
/// Set next_gc_full_heap to the given value.
@@ -272,3 +297,22 @@ impl<VM: VMBinding> Gen<VM> {
272297
self.nursery.reserved_pages() + self.common.get_used_pages()
273298
}
274299
}
300+
301+
/// This trait include methods that are specific to generational plans.
302+
pub trait GenerationalPlan: Plan {
303+
/// Return the common generational implementation [`crate::plan::generational::global::CommonGenPlan`].
304+
fn common_gen(&self) -> &CommonGenPlan<Self::VM>;
305+
306+
/// Return the number of pages available for allocation into the mature space.
307+
fn get_mature_physical_pages_available(&self) -> usize;
308+
309+
/// Return the number of used pages in the mature space.
310+
fn get_mature_reserved_pages(&self) -> usize;
311+
}
312+
313+
/// Is current GC only collecting objects allocated since last GC? This method can be called
314+
/// with any plan (generational or not). For non generational plans, it will always return false.
315+
pub fn is_nursery_gc<VM: VMBinding>(plan: &dyn Plan<VM = VM>) -> bool {
316+
plan.generational()
317+
.map_or(false, |plan| plan.common_gen().is_current_gc_nursery())
318+
}

0 commit comments

Comments
 (0)