Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement mem balancer #724

Merged
merged 29 commits into from
Jan 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c31de17
Add GCTriggerSelector
qinsoon Dec 13, 2022
1d5c692
Add an option for gc_trigger
qinsoon Dec 13, 2022
92ce2cf
Refactoring args for creating plans and spaces.
qinsoon Dec 13, 2022
3f496eb
Remove Plan::poll. Add struct GCTrigger. Rename old trait GCTrigger to
qinsoon Dec 13, 2022
e3d98b0
Call on_gc_start/end
qinsoon Dec 13, 2022
cbf398b
Rename GCTriggerPolicySelector to GCTriggerSelector. Add info logging
qinsoon Dec 14, 2022
05159f8
Fix build/test/style
qinsoon Dec 14, 2022
9053110
Fix doc
qinsoon Dec 14, 2022
1a4d38d
Some cleanup
qinsoon Dec 14, 2022
39c038f
Fix tests on 32 bits
qinsoon Dec 14, 2022
d5a9300
Implement complete MemBalancer. Add on_gc_release for GC trigger.
qinsoon Dec 15, 2022
9aa0cba
Add can_heap_size_grow for GCTriggerPolicy. We ony do emergency GC if
qinsoon Dec 15, 2022
a0dd8f1
Use regex to parsing. Separate validation from parsing. Allow T as size
qinsoon Dec 16, 2022
1ef4e85
Merge branch 'gc-trigger-option' into complete-mem-balancer
qinsoon Dec 16, 2022
0e23ce0
Merge branch 'master' into complete-mem-balancer
qinsoon Dec 18, 2022
dd29f39
Minor fix
qinsoon Dec 19, 2022
8431b7a
Add Plan.end_of_gc. Generational plans set next_heap_full_size in
qinsoon Dec 19, 2022
c34c6b4
Fix typo
qinsoon Dec 21, 2022
9eb4dbf
Merge branch 'plan-end-of-gc' into complete-mem-balancer
qinsoon Dec 21, 2022
325e412
Update mem balancer for generational plan
qinsoon Jan 3, 2023
c3b0d6a
Merge branch 'master' into complete-mem-balancer
qinsoon Jan 3, 2023
a55866b
Tidy up
qinsoon Jan 3, 2023
fe3879c
Merge branch 'master' into complete-mem-balancer
qinsoon Jan 5, 2023
21ac93a
Use AtomicRefCell instead of Atomic for MemBalancerStats
qinsoon Jan 5, 2023
5aa7023
Add trait GenerationalPlan
qinsoon Jan 12, 2023
b2f83c7
Fix typo in comments
qinsoon Jan 12, 2023
1b88d90
Use Option<f64> for MemBalancerStats previous values
qinsoon Jan 12, 2023
02d2962
Merge branch 'master' into complete-mem-balancer
qinsoon Jan 12, 2023
c72eb5e
Rename GenerationalPlan::gen to common_gen. Rename Gen to CommonGenPlan.
qinsoon Jan 12, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/plan/generational/barrier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,21 @@ use crate::MMTK;
use super::gc_work::GenNurseryProcessEdges;
use super::gc_work::ProcessModBuf;
use super::gc_work::ProcessRegionModBuf;
use super::global::Gen;
use super::global::CommonGenPlan;

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

impl<VM: VMBinding> GenObjectBarrierSemantics<VM> {
pub fn new(mmtk: &'static MMTK<VM>, gen: &'static Gen<VM>) -> Self {
pub fn new(mmtk: &'static MMTK<VM>, gen: &'static CommonGenPlan<VM>) -> Self {
Self {
mmtk,
gen,
Expand Down
33 changes: 20 additions & 13 deletions src/plan/generational/copying/global.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use super::gc_work::GenCopyGCWorkContext;
use super::gc_work::GenCopyNurseryGCWorkContext;
use super::mutator::ALLOCATOR_MAPPING;
use crate::plan::generational::global::Gen;
use crate::plan::generational::global::CommonGenPlan;
use crate::plan::generational::global::GenerationalPlan;
use crate::plan::global::BasePlan;
use crate::plan::global::CommonPlan;
use crate::plan::global::CreateGeneralPlanArgs;
Expand All @@ -27,7 +28,7 @@ use mmtk_macros::PlanTraceObject;
#[derive(PlanTraceObject)]
pub struct GenCopy<VM: VMBinding> {
#[fallback_trace]
pub gen: Gen<VM>,
pub gen: CommonGenPlan<VM>,
pub hi: AtomicBool,
#[trace(CopySemantics::Mature)]
pub copyspace0: CopySpace<VM>,
Expand Down Expand Up @@ -98,7 +99,7 @@ impl<VM: VMBinding> Plan for GenCopy<VM> {
}

fn prepare(&mut self, tls: VMWorkerThread) {
let full_heap = !self.is_current_gc_nursery();
let full_heap = !self.gen.is_current_gc_nursery();
self.gen.prepare(tls);
if full_heap {
self.hi
Expand All @@ -118,7 +119,7 @@ impl<VM: VMBinding> Plan for GenCopy<VM> {
}

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

fn end_of_gc(&mut self, _tls: VMWorkerThread) {
self.gen
.set_next_gc_full_heap(Gen::should_next_gc_be_full_heap(self));
.set_next_gc_full_heap(CommonGenPlan::should_next_gc_be_full_heap(self));
}

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

fn get_mature_physical_pages_available(&self) -> usize {
self.tospace().available_physical_pages()
}

fn base(&self) -> &BasePlan<VM> {
&self.gen.common.base
}
Expand All @@ -159,12 +156,22 @@ impl<VM: VMBinding> Plan for GenCopy<VM> {
&self.gen.common
}

fn generational(&self) -> &Gen<VM> {
fn generational(&self) -> Option<&dyn GenerationalPlan<VM = Self::VM>> {
Some(self)
}
}

impl<VM: VMBinding> GenerationalPlan for GenCopy<VM> {
fn common_gen(&self) -> &CommonGenPlan<Self::VM> {
&self.gen
}

fn is_current_gc_nursery(&self) -> bool {
!self.gen.gc_full_heap.load(Ordering::SeqCst)
fn get_mature_physical_pages_available(&self) -> usize {
self.tospace().available_physical_pages()
}

fn get_mature_reserved_pages(&self) -> usize {
self.tospace().reserved_pages()
}
}

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

let res = GenCopy {
gen: Gen::new(plan_args),
gen: CommonGenPlan::new(plan_args),
hi: AtomicBool::new(false),
copyspace0,
copyspace1,
Expand Down
22 changes: 17 additions & 5 deletions src/plan/generational/gc_work.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use atomic::Ordering;

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

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

fn new(edges: Vec<EdgeOf<Self>>, roots: bool, mmtk: &'static MMTK<VM>) -> Self {
let base = ProcessEdgesBase::new(edges, roots, mmtk);
let gen = base.plan().generational();
let gen = base.plan().generational().unwrap().common_gen();
Self { gen, base }
}
#[inline]
Expand Down Expand Up @@ -95,7 +95,13 @@ impl<E: ProcessEdgesWork> GCWork<E::VM> for ProcessModBuf<E> {
);
}
// scan modbuf only if the current GC is a nursery GC
if mmtk.plan.is_current_gc_nursery() {
if mmtk
.plan
.generational()
.unwrap()
.common_gen()
.is_current_gc_nursery()
{
// Scan objects in the modbuf and forward pointers
let modbuf = std::mem::take(&mut self.modbuf);
GCWork::do_work(
Expand Down Expand Up @@ -129,7 +135,13 @@ impl<E: ProcessEdgesWork> GCWork<E::VM> for ProcessRegionModBuf<E> {
#[inline(always)]
fn do_work(&mut self, worker: &mut GCWorker<E::VM>, mmtk: &'static MMTK<E::VM>) {
// Scan modbuf only if the current GC is a nursery GC
if mmtk.plan.is_current_gc_nursery() {
if mmtk
.plan
.generational()
.unwrap()
.common_gen()
.is_current_gc_nursery()
{
// Collect all the entries in all the slices
let mut edges = vec![];
for slice in &self.modbuf {
Expand Down
72 changes: 58 additions & 14 deletions src/plan/generational/global.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use crate::plan::Plan;
use crate::policy::copyspace::CopySpace;
use crate::policy::space::Space;
use crate::scheduler::*;
use crate::util::conversions;
use crate::util::copy::CopySemantics;
use crate::util::heap::VMRequest;
use crate::util::metadata::side_metadata::SideMetadataSanity;
Expand All @@ -22,7 +21,7 @@ use mmtk_macros::PlanTraceObject;
/// Common implementation for generational plans. Each generational plan
/// should include this type, and forward calls to it where possible.
#[derive(PlanTraceObject)]
pub struct Gen<VM: VMBinding> {
pub struct CommonGenPlan<VM: VMBinding> {
/// The nursery space.
#[trace(CopySemantics::PromoteToMature)]
pub nursery: CopySpace<VM>,
Expand All @@ -36,21 +35,21 @@ pub struct Gen<VM: VMBinding> {
pub full_heap_gc_count: Arc<Mutex<EventCounter>>,
}

impl<VM: VMBinding> Gen<VM> {
impl<VM: VMBinding> CommonGenPlan<VM> {
pub fn new(mut args: CreateSpecificPlanArgs<VM>) -> Self {
let nursery = CopySpace::new(
args.get_space_args(
"nursery",
true,
VMRequest::fixed_extent(args.global_args.options.get_max_nursery(), false),
VMRequest::fixed_extent(args.global_args.options.get_max_nursery_bytes(), false),
),
true,
);
let common = CommonPlan::new(args);

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

Gen {
CommonGenPlan {
nursery,
common,
gc_full_heap: AtomicBool::default(),
Expand Down Expand Up @@ -97,28 +96,35 @@ impl<VM: VMBinding> Gen<VM> {
///
/// Returns `true` if the nursery has grown to the extent that it may not be able to be copied
/// into the mature space.
fn virtual_memory_exhausted<P: Plan>(&self, plan: &P) -> bool {
fn virtual_memory_exhausted(plan: &dyn GenerationalPlan<VM = VM>) -> bool {
((plan.get_collection_reserved_pages() as f64
* VM::VMObjectModel::VM_WORST_CASE_COPY_EXPANSION) as usize)
> plan.get_mature_physical_pages_available()
}

/// Check if we need a GC based on the nursery space usage. This method may mark
/// the following GC as a full heap GC.
pub fn collection_required<P: Plan>(
pub fn collection_required<P: Plan<VM = VM>>(
&self,
plan: &P,
space_full: bool,
space: Option<&dyn Space<VM>>,
) -> bool {
let nursery_full = self.nursery.reserved_pages()
>= (conversions::bytes_to_pages_up(self.common.base.options.get_max_nursery()));
let cur_nursery = self.nursery.reserved_pages();
let max_nursery = self.common.base.options.get_max_nursery_pages();
let nursery_full = cur_nursery >= max_nursery;
trace!(
"nursery_full = {:?} (nursery = {}, max_nursery = {})",
nursery_full,
cur_nursery,
max_nursery,
);

if nursery_full {
return true;
}

if self.virtual_memory_exhausted(plan) {
if Self::virtual_memory_exhausted(plan.generational().unwrap()) {
return true;
}

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

/// Check if we should do a full heap GC. It returns true if we should have a full heap GC.
/// It also sets gc_full_heap based on the result.
pub fn requires_full_heap_collection<P: Plan>(&self, plan: &P) -> bool {
pub fn requires_full_heap_collection<P: Plan<VM = VM>>(&self, plan: &P) -> bool {
// Allow the same 'true' block for if-else.
// The conditions are complex, and it is easier to read if we put them to separate if blocks.
#[allow(clippy::if_same_then_else, clippy::needless_bool)]
let is_full_heap = if crate::plan::generational::FULL_NURSERY_GC {
trace!("full heap: forced full heap");
// For barrier overhead measurements, we always do full gc in nursery collections.
true
} else if self
Expand All @@ -160,6 +167,7 @@ impl<VM: VMBinding> Gen<VM> {
.load(Ordering::SeqCst)
&& *self.common.base.options.full_heap_system_gc
{
trace!("full heap: user triggered");
// User triggered collection, and we force full heap for user triggered collection
true
} else if self.next_gc_full_heap.load(Ordering::SeqCst)
Expand All @@ -170,9 +178,18 @@ impl<VM: VMBinding> Gen<VM> {
.load(Ordering::SeqCst)
> 1
{
trace!(
"full heap: next_gc_full_heap = {}, cur_collection_attempts = {}",
self.next_gc_full_heap.load(Ordering::SeqCst),
self.common
.base
.cur_collection_attempts
.load(Ordering::SeqCst)
);
// Forces full heap collection
true
} else if self.virtual_memory_exhausted(plan) {
} else if Self::virtual_memory_exhausted(plan.generational().unwrap()) {
trace!("full heap: virtual memory exhausted");
true
} else {
// We use an Appel-style nursery. The default GC (even for a "heap-full" collection)
Expand Down Expand Up @@ -250,8 +267,16 @@ impl<VM: VMBinding> Gen<VM> {
/// [`get_available_pages`](crate::plan::Plan::get_available_pages)
/// whose value depends on which spaces have been released.
pub fn should_next_gc_be_full_heap(plan: &dyn Plan<VM = VM>) -> bool {
plan.get_available_pages()
< conversions::bytes_to_pages_up(plan.base().options.get_min_nursery())
let available = plan.get_available_pages();
let min_nursery = plan.base().options.get_min_nursery_pages();
let next_gc_full_heap = available < min_nursery;
trace!(
"next gc will be full heap? {}, availabe pages = {}, min nursery = {}",
next_gc_full_heap,
available,
min_nursery
);
next_gc_full_heap
}

/// Set next_gc_full_heap to the given value.
Expand All @@ -272,3 +297,22 @@ impl<VM: VMBinding> Gen<VM> {
self.nursery.reserved_pages() + self.common.get_used_pages()
}
}

/// This trait include methods that are specific to generational plans.
pub trait GenerationalPlan: Plan {
/// Return the common generational implementation [`crate::plan::generational::global::CommonGenPlan`].
fn common_gen(&self) -> &CommonGenPlan<Self::VM>;

/// Return the number of pages available for allocation into the mature space.
fn get_mature_physical_pages_available(&self) -> usize;

/// Return the number of used pages in the mature space.
fn get_mature_reserved_pages(&self) -> usize;
}

/// Is current GC only collecting objects allocated since last GC? This method can be called
/// with any plan (generational or not). For non generational plans, it will always return false.
pub fn is_nursery_gc<VM: VMBinding>(plan: &dyn Plan<VM = VM>) -> bool {
plan.generational()
.map_or(false, |plan| plan.common_gen().is_current_gc_nursery())
}
Loading