use rayon::Scope;
use super::bitset::{AtomicBitSet, BitSet};
use super::graph::SectionGraph;
use crate::ids::SectionId;
const SPLIT_THRESHOLD: usize = 256;
const LAZY_SPLIT_THRESHOLD: usize = 32;
#[derive(Debug)]
pub struct GcMarker<'g> {
graph: &'g SectionGraph,
bits: AtomicBitSet,
}
impl<'g> GcMarker<'g> {
#[must_use]
pub fn new(graph: &'g SectionGraph) -> Self {
Self {
graph,
bits: AtomicBitSet::new(graph.num_sections()),
}
}
pub fn mark_roots(&self) {
self.mark_from(self.graph.roots());
}
pub fn mark_from(&self, roots: &[SectionId]) {
let graph = self.graph;
let bits = &self.bits;
let parallel = rayon::current_num_threads() > 1;
if !parallel {
let stack: Vec<SectionId> = roots
.iter()
.copied()
.filter(|root| bits.set(root.index()))
.collect();
mark_sequential(graph, bits, stack);
return;
}
rayon::scope(|scope| {
for chunk in roots.chunks(SPLIT_THRESHOLD) {
let stack: Vec<SectionId> = chunk
.iter()
.copied()
.filter(|root| bits.set(root.index()))
.collect();
if !stack.is_empty() {
scope.spawn(move |scope| mark_task(scope, graph, bits, stack));
}
}
});
}
#[must_use]
pub fn is_marked(&self, section: SectionId) -> bool {
self.bits.get(section.index())
}
#[must_use]
pub fn finish(self) -> LiveSet {
LiveSet {
bits: self.bits.into_bitset(),
}
}
}
fn mark_sequential(graph: &SectionGraph, bits: &AtomicBitSet, mut stack: Vec<SectionId>) {
while let Some(section) = stack.pop() {
for &target in graph.edges(section) {
if bits.set(target.index()) {
stack.push(target);
}
}
}
}
fn mark_task<'s>(
scope: &Scope<'s>,
graph: &'s SectionGraph,
bits: &'s AtomicBitSet,
mut stack: Vec<SectionId>,
) {
while let Some(section) = stack.pop() {
for &target in graph.edges(section) {
if bits.set(target.index()) {
stack.push(target);
}
}
if stack.len() > SPLIT_THRESHOLD {
let half = stack.split_off(stack.len() / 2);
scope.spawn(move |scope| mark_task(scope, graph, bits, half));
}
}
}
pub fn mark_reachable<E>(sections: usize, roots: &[SectionId], edges: &E) -> LiveSet
where
E: Fn(SectionId, &mut Vec<SectionId>) + Sync,
{
let marks = AtomicBitSet::new(sections);
let bits = &marks;
if rayon::current_num_threads() <= 1 {
let mut stack: Vec<SectionId> = roots
.iter()
.copied()
.filter(|root| bits.set(root.index()))
.collect();
let mut targets = Vec::new();
while let Some(section) = stack.pop() {
edges(section, &mut targets);
stack.extend(targets.drain(..).filter(|target| bits.set(target.index())));
}
} else {
rayon::scope(|scope| {
for chunk in roots.chunks(LAZY_SPLIT_THRESHOLD) {
let stack: Vec<SectionId> = chunk
.iter()
.copied()
.filter(|root| bits.set(root.index()))
.collect();
if !stack.is_empty() {
scope.spawn(move |scope| mark_lazy_task(scope, edges, bits, stack));
}
}
});
}
LiveSet {
bits: marks.into_bitset(),
}
}
fn mark_lazy_task<'s, E>(
scope: &Scope<'s>,
edges: &'s E,
bits: &'s AtomicBitSet,
mut stack: Vec<SectionId>,
) where
E: Fn(SectionId, &mut Vec<SectionId>) + Sync,
{
let mut targets = Vec::new();
while let Some(section) = stack.pop() {
edges(section, &mut targets);
stack.extend(targets.drain(..).filter(|target| bits.set(target.index())));
if stack.len() > LAZY_SPLIT_THRESHOLD {
let half = stack.split_off(stack.len() / 2);
scope.spawn(move |scope| mark_lazy_task(scope, edges, bits, half));
}
}
}
#[must_use]
pub fn collect_garbage(graph: &SectionGraph) -> LiveSet {
let marker = GcMarker::new(graph);
marker.mark_roots();
marker.finish()
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct LiveSet {
bits: BitSet,
}
impl LiveSet {
#[inline]
#[must_use]
pub fn is_live(&self, section: SectionId) -> bool {
self.bits.get(section.index())
}
#[must_use]
pub fn num_sections(&self) -> usize {
self.bits.len()
}
#[must_use]
pub fn num_live(&self) -> usize {
self.bits.count_ones()
}
pub fn live(&self) -> impl Iterator<Item = SectionId> + '_ {
self.bits.ones().map(SectionId::new)
}
#[must_use]
pub fn removed(&self) -> Vec<SectionId> {
self.bits.zeros().map(SectionId::new).collect()
}
#[must_use]
pub fn bits(&self) -> &BitSet {
&self.bits
}
}
const UNREACHED: u32 = u32::MAX;
#[derive(Clone, Debug)]
pub struct ReferenceTree {
parent: Vec<u32>,
}
impl ReferenceTree {
#[must_use]
pub fn new(graph: &SectionGraph) -> Self {
Self::search(graph, None)
}
fn search(graph: &SectionGraph, target: Option<SectionId>) -> Self {
let mut parent = vec![UNREACHED; graph.num_sections()];
let mut queue: Vec<SectionId> = Vec::new();
for &root in graph.roots() {
if let Some(slot) = parent.get_mut(root.index())
&& *slot == UNREACHED
{
*slot = root.as_u32();
queue.push(root);
}
}
let mut head = 0;
while let Some(§ion) = queue.get(head) {
if target.is_some_and(|target| parent.get(target.index()) != Some(&UNREACHED)) {
break;
}
head += 1;
for &next in graph.edges(section) {
if let Some(slot) = parent.get_mut(next.index())
&& *slot == UNREACHED
{
*slot = section.as_u32();
queue.push(next);
}
}
}
Self { parent }
}
#[must_use]
pub fn chain(&self, section: SectionId) -> Option<Vec<SectionId>> {
let mut current = section.as_u32();
if self
.parent
.get(section.index())
.is_none_or(|&p| p == UNREACHED)
{
return None;
}
let mut chain = vec![section];
for _ in 0..self.parent.len() {
let parent = *self.parent.get(current as usize)?;
if parent == current {
chain.reverse();
return Some(chain);
}
chain.push(SectionId::from_u32(parent));
current = parent;
}
None
}
}
#[must_use]
pub fn why_live(graph: &SectionGraph, section: SectionId) -> Option<Vec<SectionId>> {
ReferenceTree::search(graph, Some(section)).chain(section)
}
#[cfg(test)]
mod tests {
use super::super::graph::GraphBuilder;
use super::*;
fn id(index: usize) -> SectionId {
SectionId::new(index)
}
#[test]
fn marks_reachable_including_cycles() {
let mut builder = GraphBuilder::new(6);
builder.add_edge(id(0), id(1));
builder.add_edge(id(1), id(2));
builder.add_edge(id(2), id(1));
builder.add_edge(id(3), id(4));
builder.add_edge(id(4), id(3));
builder.add_root(id(0));
builder.add_root(id(5));
builder.add_root(id(0));
let graph = builder.build().unwrap();
let live = collect_garbage(&graph);
assert_eq!(
live.live().collect::<Vec<_>>(),
vec![id(0), id(1), id(2), id(5)]
);
assert_eq!(live.removed(), vec![id(3), id(4)]);
assert_eq!(live.num_live(), 4);
assert_eq!(why_live(&graph, id(2)), Some(vec![id(0), id(1), id(2)]));
assert_eq!(why_live(&graph, id(5)), Some(vec![id(5)]));
assert_eq!(why_live(&graph, id(3)), None);
assert_eq!(why_live(&graph, id(99)), None);
}
#[test]
fn lazy_mark_matches_graph_mark() {
let sections = 20_000usize;
let mut builder = GraphBuilder::new(sections);
let mut adjacency: Vec<Vec<SectionId>> = vec![Vec::new(); sections];
let mut state = 0x2545_f491_4f6c_dd1du64;
let mut next = || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
state
};
for (from, targets) in adjacency.iter_mut().enumerate() {
if from + 1 < sections && next() % 4 != 0 {
targets.push(id(from + 1));
}
for _ in 0..next() % 3 {
let to = (next() % sections as u64) as usize;
if next() % 2 == 0 {
targets.push(id(to));
}
}
}
for (from, targets) in adjacency.iter().enumerate() {
for &to in targets {
builder.add_edge(id(from), to);
}
}
let roots: Vec<SectionId> = (0..sections).step_by(997).map(id).collect();
for &root in &roots {
builder.add_root(root);
}
let graph = builder.build().unwrap();
let expected = collect_garbage(&graph);
for threads in [1, 3, 8] {
let pool = rayon::ThreadPoolBuilder::new()
.num_threads(threads)
.build()
.unwrap();
let lazy = pool.install(|| {
mark_reachable(sections, &roots, &|section, targets| {
targets.extend_from_slice(&adjacency[section.index()]);
})
});
assert_eq!(lazy, expected, "{threads} threads");
}
}
#[test]
fn marker_supports_extra_rounds() {
let mut builder = GraphBuilder::new(3);
builder.add_edge(id(1), id(2));
builder.add_root(id(0));
let graph = builder.build().unwrap();
let marker = GcMarker::new(&graph);
marker.mark_roots();
assert!(!marker.is_marked(id(1)));
marker.mark_from(&[id(1), id(42)]);
assert_eq!(marker.finish().removed(), Vec::<SectionId>::new());
}
#[test]
fn why_live_prefers_shortest_then_earliest() {
let mut builder = GraphBuilder::new(6);
builder.add_edge(id(0), id(4));
builder.add_edge(id(4), id(5));
builder.add_edge(id(5), id(3));
builder.add_edge(id(0), id(2));
builder.add_edge(id(0), id(1));
builder.add_edge(id(1), id(3));
builder.add_edge(id(2), id(3));
builder.add_root(id(0));
let graph = builder.build().unwrap();
assert_eq!(why_live(&graph, id(3)), Some(vec![id(0), id(2), id(3)]));
let tree = ReferenceTree::new(&graph);
assert_eq!(tree.chain(id(5)), Some(vec![id(0), id(4), id(5)]));
}
}