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

Adding feature to query the fragmentation of immixspace #1089

Draft
wants to merge 14 commits into
base: master
Choose a base branch
from
Draft
Changes from 1 commit
Commits
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
Prev Previous commit
Next Next commit
Adding feature to dump memory stats (from los and immixspace)
  • Loading branch information
udesou committed Mar 4, 2024
commit 06284e1e19af239a2cf1e9c6b48acedb9501f561
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -153,8 +153,8 @@ malloc_counted_size = []
# Count the size of all live objects in GC
count_live_bytes_in_gc = []

# Count the size of live objects in immixspace (the ones that can actually be opportunistically moved)
count_live_bytes_immixspace = []
# Dump memory stats about the plan (live bytes, live lines, live blocks, and used pages)
dump_memory_stats = []

# Workaround a problem where bpftrace scripts (see tools/tracing/timeline/capture.bt) cannot
# capture the type names of work packets.
24 changes: 0 additions & 24 deletions src/memory_manager.rs
Original file line number Diff line number Diff line change
@@ -557,30 +557,6 @@ pub fn live_bytes_in_last_gc<VM: VMBinding>(mmtk: &MMTK<VM>) -> usize {
mmtk.state.live_bytes_in_last_gc.load(Ordering::SeqCst)
}

/// Return the percentage of occupation of the immixspace (e.g. 42.98 percent). To do that we count the size of every live object
/// in the immixspace. Since MMTk accounts for memory in pages, we return the ratio between this number and
/// the number of used bytes (according to the used pages by the immixspace).
/// The value returned by this method is only updated when we finish tracing in a GC. A recommended timing
/// to call this method is at the end of a GC (e.g. when the runtime is about to resume threads).
#[cfg(feature = "count_live_bytes_immixspace")]
pub fn occupation_rate_in_immixspace<VM: VMBinding>(mmtk: &MMTK<VM>) -> f64 {
use crate::policy::immix::ImmixSpace;
use crate::policy::space::Space;
let mut rate = None;
mmtk.get_plan()
.for_each_space(&mut |space: &dyn Space<VM>| {
if let Some(immix) = space.downcast_ref::<ImmixSpace<VM>>() {
assert!(
rate.is_none(),
"There are multiple Immix spaces in the plan."
);
// Get the stats here from ImmixSpace
rate = Some(immix.get_occupation_rate());
}
});
rate.expect("No Immix space in the plan.") as f64 / 100.0
}

/// Return the starting address of the heap. *Note that currently MMTk uses
/// a fixed address range as heap.*
pub fn starting_heap_address() -> Address {
4 changes: 4 additions & 0 deletions src/plan/global.rs
Original file line number Diff line number Diff line change
@@ -315,6 +315,10 @@ pub trait Plan: 'static + HasSpaces + Sync + Downcast {
space.verify_side_metadata_sanity(&mut side_metadata_sanity_checker);
})
}

// Dump memory stats for the plan
#[cfg(feature = "dump_memory_stats")]
fn dump_memory_stats(&self) {}
}

impl_downcast!(Plan assoc VM);
6 changes: 6 additions & 0 deletions src/plan/immix/global.rs
Original file line number Diff line number Diff line change
@@ -116,6 +116,12 @@ impl<VM: VMBinding> Plan for Immix<VM> {
fn common(&self) -> &CommonPlan<VM> {
&self.common
}

#[cfg(feature = "dump_memory_stats")]
fn dump_memory_stats(&self) {
self.immix_space.dump_memory_stats();
self.common.los.dump_memory_stats();
}
}

impl<VM: VMBinding> Immix<VM> {
91 changes: 35 additions & 56 deletions src/policy/immix/immixspace.rs
Original file line number Diff line number Diff line change
@@ -54,11 +54,9 @@ pub struct ImmixSpace<VM: VMBinding> {
scheduler: Arc<GCWorkScheduler<VM>>,
/// Some settings for this space
space_args: ImmixSpaceArgs,
/// Keeping track of fragmentation rate
#[cfg(feature = "count_live_bytes_immixspace")]
live_bytes_in_immixspace: AtomicUsize,
#[cfg(feature = "count_live_bytes_immixspace")]
occupation_rate: AtomicUsize,
/// Keeping track of live bytes
#[cfg(feature = "dump_memory_stats")]
live_bytes: AtomicUsize,
}

/// Some arguments for Immix Space.
@@ -223,9 +221,8 @@ impl<VM: VMBinding> crate::policy::gc_work::PolicyTraceObject<VM> for ImmixSpace
self.mark_lines(object);
}

// count the bytes for each object in immixspace to
// check for fragmentation
#[cfg(feature = "count_live_bytes_immixspace")]
// count the bytes for each object in immixspace
#[cfg(feature = "dump_memory_stats")]
self.increase_live_bytes(VM::VMObjectModel::get_current_size(object));
}

@@ -325,10 +322,8 @@ impl<VM: VMBinding> ImmixSpace<VM> {
mark_state: Self::MARKED_STATE,
scheduler: scheduler.clone(),
space_args,
#[cfg(feature = "count_live_bytes_immixspace")]
live_bytes_in_immixspace: AtomicUsize::new(0),
#[cfg(feature = "count_live_bytes_immixspace")]
occupation_rate: AtomicUsize::new(0),
#[cfg(feature = "dump_memory_stats")]
live_bytes: AtomicUsize::new(0),
}
}

@@ -451,7 +446,7 @@ impl<VM: VMBinding> ImmixSpace<VM> {
}
}

#[cfg(feature = "count_live_bytes_immixspace")]
#[cfg(feature = "dump_memory_stats")]
self.set_live_bytes(0);
}

@@ -481,14 +476,11 @@ impl<VM: VMBinding> ImmixSpace<VM> {

self.lines_consumed.store(0, Ordering::Relaxed);

// calculate the fragmentation rate
#[cfg(feature = "count_live_bytes_immixspace")]
self.dump_memory_stats();

did_defrag
}

fn dump_memory_stats(&mut self) {
#[cfg(feature = "dump_memory_stats")]
pub(crate) fn dump_memory_stats(&self) {
#[derive(Default)]
struct Dist {
live_blocks: usize,
@@ -505,11 +497,18 @@ impl<VM: VMBinding> ImmixSpace<VM> {
.filter(|b| b.get_state() != BlockState::Unallocated)
{
dist.live_blocks += 1;
for _line in block
.lines()
.filter(|l| l.is_marked(self.line_mark_state.load(Ordering::Acquire)))
{
dist.live_lines = 1;
match block.get_state() {
BlockState::Marked => {
panic!("At this point the block should have been swept already");
}
BlockState::Unmarked => {
// Block is unmarked and cannot be reused (has no holes)
dist.live_lines += Block::LINES;
}
BlockState::Reusable { unavailable_lines } => {
dist.live_lines += unavailable_lines as usize;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this number equivalent to linearly scan the line mark table and manually check the line mark bytes? Probably better to add an assertion to double check. Same as L506.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added an assertion in the latest commit that compares the number of live lines by just iterating the lines in the block and checking the mark table with the number from the block state. Is that what you meant?

}
BlockState::Unallocated => {}
}
}
}
@@ -518,31 +517,22 @@ impl<VM: VMBinding> ImmixSpace<VM> {
"{} immixspace",
chrono::offset::Local::now().format("%Y-%m-%d %H:%M:%S")
);
println!("Live bytes = {}", self.get_live_bytes());
println!("Reserved pages = {}", self.reserved_pages());
println!("\tLive bytes = {}", self.get_live_bytes());
println!("\tReserved pages = {}", self.reserved_pages());
println!(
"Reserved pages (bytes) = {}",
"\tReserved pages (bytes) = {}",
self.reserved_pages() << LOG_BYTES_IN_PAGE
);
println!("Live blocks = {}", dist.live_blocks);
println!("\tLive blocks = {}", dist.live_blocks);
println!(
"Live blocks (bytes) = {}",
"\tLive blocks (bytes) = {}",
dist.live_blocks << Block::LOG_BYTES
);
println!("Live lines = {}", dist.live_lines);
println!("\tLive lines = {}", dist.live_lines);
println!(
"Live lines (bytes) = {}",
"\tLive lines (bytes) = {}",
dist.live_lines << Line::LOG_BYTES
);

let o_rate: f64 =
self.get_live_bytes() as f64 / (self.reserved_pages() << LOG_BYTES_IN_PAGE) as f64;

let o_rate_usize: usize = (o_rate * 10000.0) as usize;

debug_assert!((0.0..=1.0).contains(&o_rate));

self.set_occupation_rate(o_rate_usize);
}

/// Generate chunk sweep tasks
@@ -886,30 +876,19 @@ impl<VM: VMBinding> ImmixSpace<VM> {
}
}

#[cfg(feature = "count_live_bytes_immixspace")]
#[cfg(feature = "dump_memory_stats")]
pub fn get_live_bytes(&self) -> usize {
self.live_bytes_in_immixspace.load(Ordering::SeqCst)
self.live_bytes.load(Ordering::SeqCst)
}

#[cfg(feature = "count_live_bytes_immixspace")]
#[cfg(feature = "dump_memory_stats")]
pub fn set_live_bytes(&self, size: usize) {
self.live_bytes_in_immixspace.store(size, Ordering::SeqCst)
self.live_bytes.store(size, Ordering::SeqCst)
}

#[cfg(feature = "count_live_bytes_immixspace")]
#[cfg(feature = "dump_memory_stats")]
pub fn increase_live_bytes(&self, size: usize) {
self.live_bytes_in_immixspace
.fetch_add(size, Ordering::SeqCst);
}

#[cfg(feature = "count_live_bytes_immixspace")]
pub fn get_occupation_rate(&self) -> usize {
self.occupation_rate.load(Ordering::SeqCst)
}

#[cfg(feature = "count_live_bytes_immixspace")]
pub fn set_occupation_rate(&self, size: usize) {
self.occupation_rate.store(size, Ordering::SeqCst);
self.live_bytes.fetch_add(size, Ordering::SeqCst);
}
}

45 changes: 45 additions & 0 deletions src/policy/largeobjectspace.rs
Original file line number Diff line number Diff line change
@@ -6,13 +6,17 @@ use crate::policy::sft::GCWorkerMutRef;
use crate::policy::sft::SFT;
use crate::policy::space::{CommonSpace, Space};
use crate::util::constants::BYTES_IN_PAGE;
#[cfg(feature = "dump_memory_stats")]
use crate::util::constants::LOG_BYTES_IN_PAGE;
use crate::util::heap::{FreeListPageResource, PageResource};
use crate::util::metadata;
use crate::util::opaque_pointer::*;
use crate::util::treadmill::TreadMill;
use crate::util::{Address, ObjectReference};
use crate::vm::ObjectModel;
use crate::vm::VMBinding;
#[cfg(feature = "dump_memory_stats")]
use std::sync::atomic::AtomicUsize;

#[allow(unused)]
const PAGE_MASK: usize = !(BYTES_IN_PAGE - 1);
@@ -28,6 +32,9 @@ pub struct LargeObjectSpace<VM: VMBinding> {
mark_state: u8,
in_nursery_gc: bool,
treadmill: TreadMill,
/// Keeping track of live bytes
#[cfg(feature = "dump_memory_stats")]
live_bytes: AtomicUsize,
}

impl<VM: VMBinding> SFT for LargeObjectSpace<VM> {
@@ -136,6 +143,11 @@ impl<VM: VMBinding> crate::policy::gc_work::PolicyTraceObject<VM> for LargeObjec
fn may_move_objects<const KIND: crate::policy::gc_work::TraceKind>() -> bool {
false
}

#[cfg(feature = "dump_memory_stats")]
fn post_scan_object(&self, object: ObjectReference) {
self.increase_live_bytes(VM::VMObjectModel::get_current_size(object));
}
}

impl<VM: VMBinding> LargeObjectSpace<VM> {
@@ -162,6 +174,8 @@ impl<VM: VMBinding> LargeObjectSpace<VM> {
mark_state: 0,
in_nursery_gc: false,
treadmill: TreadMill::new(),
#[cfg(feature = "dump_memory_stats")]
live_bytes: AtomicUsize::new(0),
}
}

@@ -172,6 +186,8 @@ impl<VM: VMBinding> LargeObjectSpace<VM> {
}
self.treadmill.flip(full_heap);
self.in_nursery_gc = !full_heap;
#[cfg(feature = "dump_memory_stats")]
self.set_live_bytes(0);
}

pub fn release(&mut self, full_heap: bool) {
@@ -303,6 +319,35 @@ impl<VM: VMBinding> LargeObjectSpace<VM> {
) & NURSERY_BIT
== NURSERY_BIT
}

#[cfg(feature = "dump_memory_stats")]
pub fn get_live_bytes(&self) -> usize {
self.live_bytes.load(Ordering::SeqCst)
}

#[cfg(feature = "dump_memory_stats")]
pub fn set_live_bytes(&self, size: usize) {
self.live_bytes.store(size, Ordering::SeqCst)
}

#[cfg(feature = "dump_memory_stats")]
pub fn increase_live_bytes(&self, size: usize) {
self.live_bytes.fetch_add(size, Ordering::SeqCst);
}

#[cfg(feature = "dump_memory_stats")]
pub(crate) fn dump_memory_stats(&self) {
println!(
"{} los",
chrono::offset::Local::now().format("%Y-%m-%d %H:%M:%S")
);
println!("\tLive bytes = {}", self.get_live_bytes());
println!("\tReserved pages = {}", self.reserved_pages());
println!(
"\tReserved pages (bytes) = {}",
self.reserved_pages() << LOG_BYTES_IN_PAGE
);
}
}

fn get_super_page(cell: Address) -> Address {
3 changes: 3 additions & 0 deletions src/scheduler/gc_work.rs
Original file line number Diff line number Diff line change
@@ -244,6 +244,9 @@ impl<VM: VMBinding> GCWork<VM> for EndOfGC {
);
}

#[cfg(feature = "dump_memory_stats")]
mmtk.get_plan().dump_memory_stats();

// We assume this is the only running work packet that accesses plan at the point of execution
let plan_mut: &mut dyn Plan<VM = VM> = unsafe { mmtk.get_plan_mut() };
plan_mut.end_of_gc(worker.tls);
Loading