Skip to content

Allow overriding ScanObjects packet size #990

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
38 changes: 29 additions & 9 deletions src/plan/tracing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
use crate::scheduler::gc_work::{EdgeOf, ProcessEdgesWork};
use crate::scheduler::{GCWorker, WorkBucketStage};
use crate::util::ObjectReference;
use crate::vm::EdgeVisitor;
use crate::vm::{EdgeVisitor, VMBinding};

/// This trait represents an object queue to enqueue objects during tracing.
pub trait ObjectQueue {
Expand All @@ -20,15 +20,34 @@ pub type VectorObjectQueue = VectorQueue<ObjectReference>;
pub struct VectorQueue<T> {
/// Enqueued nodes.
buffer: Vec<T>,
/// Capacity of the queue.
capacity: usize,
}

impl<T> VectorQueue<T> {
/// Reserve a capacity of this on first enqueue to avoid frequent resizing.
const CAPACITY: usize = 4096;
/// The default capacity of the queue.
const DEFAULT_CAPACITY: usize = 4096;

/// Create an empty `VectorObjectQueue`.
/// Create an empty `VectorObjectQueue` with default capacity.
pub fn new() -> Self {
Self { buffer: Vec::new() }
Self::with_capacity(Self::DEFAULT_CAPACITY)
}

/// Create an empty `VectorObjectQueue` with a given capacity.
pub fn with_capacity(capacity: usize) -> Self {
Self {
buffer: Vec::new(),
capacity,
}
}

/// Create an empty `VectorObjectQueue` with an optionally given capacity.
pub fn with_capacity_opt(capacity_opt: Option<usize>) -> Self {
if let Some(capacity) = capacity_opt {
Self::with_capacity(capacity)
} else {
Self::new()
}
}

/// Return `true` if the queue is empty.
Expand All @@ -46,14 +65,14 @@ impl<T> VectorQueue<T> {
self.buffer
}

/// Check if the buffer size reaches `CAPACITY`.
/// Check if the buffer size reaches the capacity.
pub fn is_full(&self) -> bool {
self.buffer.len() >= Self::CAPACITY
self.buffer.len() >= self.capacity
}

pub fn push(&mut self, v: T) {
if self.buffer.is_empty() {
self.buffer.reserve(Self::CAPACITY);
self.buffer.reserve(self.capacity);
}
self.buffer.push(v);
}
Expand All @@ -80,8 +99,9 @@ pub struct ObjectsClosure<'a, E: ProcessEdgesWork> {

impl<'a, E: ProcessEdgesWork> ObjectsClosure<'a, E> {
pub fn new(worker: &'a mut GCWorker<E::VM>, bucket: WorkBucketStage) -> Self {
let buffer = VectorQueue::with_capacity_opt(E::VM::override_scan_objects_packet_size());
Self {
buffer: VectorQueue::new(),
buffer,
worker,
bucket,
}
Expand Down
3 changes: 2 additions & 1 deletion src/scheduler/gc_work.rs
Original file line number Diff line number Diff line change
Expand Up @@ -526,9 +526,10 @@ impl<VM: VMBinding> ProcessEdgesBase<VM> {
mmtk.edge_logger.log_edge(*edge);
}
}
let nodes = VectorObjectQueue::with_capacity_opt(VM::override_scan_objects_packet_size());
Self {
edges,
nodes: VectorObjectQueue::new(),
nodes,
mmtk,
worker: std::ptr::null_mut(),
roots,
Expand Down
17 changes: 17 additions & 0 deletions src/vm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,21 @@ where
/// Note that MMTk does not attempt to do anything to align the cursor to this value, but
/// it merely asserts with this constant.
const ALLOC_END_ALIGNMENT: usize = 1;

/// Override the packet size of the `ScanObjects` work packet. If it returns `Some(size)`,
/// each `ScanObjects` work packets will contain at most `size` objects; otherwise the packet
/// size will be determined by mmtk-core.
///
/// This method is used for working around a load-balance problem on some VMs that use object-
/// enqueuing tracing. The default packet size (4096) may be too large for some workloads, in
/// which case only a few work packets will be available and most GC workers will be idle. The
/// binding can reduce the packet size to increase the level of parallelism. But if the packet
/// size is too small, it will introduce extra scheduling overhead for executing each work
/// packet.
///
/// TODO: We should support work stealing within work packets and make this workaround
/// unnecessary.
fn override_scan_objects_packet_size() -> Option<usize> {
None
}
}