use std::collections::HashMap;
#[derive(Clone, Debug, Default)]
pub struct GcHistory {
pub cycles: Vec<GcCycleRecord>,
pub max_cycles: usize,
}
impl GcHistory {
pub fn new() -> Self {
Self::default()
}
pub fn with_capacity(max_cycles: usize) -> Self {
Self {
cycles: Vec::new(),
max_cycles,
}
}
pub fn record(&mut self, record: GcCycleRecord) {
if self.max_cycles > 0 && self.cycles.len() >= self.max_cycles {
self.cycles.remove(0);
}
self.cycles.push(record);
}
pub fn total_freed(&self) -> usize {
self.cycles.iter().map(|c| c.bytes_freed).sum()
}
pub fn avg_duration_ns(&self) -> f64 {
if self.cycles.is_empty() {
0.0
} else {
self.cycles.iter().map(|c| c.duration_ns).sum::<u64>() as f64 / self.cycles.len() as f64
}
}
pub fn avg_survival_rate(&self) -> f64 {
if self.cycles.is_empty() {
1.0
} else {
self.cycles.iter().map(|c| c.survival_rate()).sum::<f64>() / self.cycles.len() as f64
}
}
pub fn major_count(&self) -> usize {
self.cycles.iter().filter(|c| c.is_major).count()
}
pub fn minor_count(&self) -> usize {
self.cycles.iter().filter(|c| !c.is_major).count()
}
pub fn last_n(&self, n: usize) -> &[GcCycleRecord] {
let start = self.cycles.len().saturating_sub(n);
&self.cycles[start..]
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BarrierAction {
None,
GraySource,
GrayDest,
SnapshotOld,
}
#[derive(Clone, Debug)]
pub struct FreeList {
pub blocks: Vec<(usize, usize)>,
pub capacity: usize,
pub allocated: usize,
}
impl FreeList {
pub fn new(capacity: usize) -> Self {
Self {
blocks: vec![(0, capacity)],
capacity,
allocated: 0,
}
}
pub fn allocate(&mut self, size: usize) -> Option<usize> {
for i in 0..self.blocks.len() {
let (offset, block_size) = self.blocks[i];
if block_size >= size {
let remaining = block_size - size;
if remaining > 0 {
self.blocks[i] = (offset + size, remaining);
} else {
self.blocks.remove(i);
}
self.allocated += size;
return Some(offset);
}
}
None
}
pub fn free(&mut self, offset: usize, size: usize) {
self.allocated = self.allocated.saturating_sub(size);
self.blocks.push((offset, size));
self.coalesce();
}
pub fn coalesce(&mut self) {
self.blocks.sort_by_key(|&(off, _)| off);
let mut i = 0;
while i + 1 < self.blocks.len() {
let (off_a, size_a) = self.blocks[i];
let (off_b, size_b) = self.blocks[i + 1];
if off_a + size_a == off_b {
self.blocks[i] = (off_a, size_a + size_b);
self.blocks.remove(i + 1);
} else {
i += 1;
}
}
}
pub fn free_block_count(&self) -> usize {
self.blocks.len()
}
pub fn free_bytes(&self) -> usize {
self.blocks.iter().map(|&(_, s)| s).sum()
}
pub fn utilization(&self) -> f64 {
if self.capacity == 0 {
0.0
} else {
self.allocated as f64 / self.capacity as f64
}
}
pub fn largest_free(&self) -> usize {
self.blocks.iter().map(|&(_, s)| s).max().unwrap_or(0)
}
}
#[derive(Clone, Debug, Default)]
pub struct GcBenchmarkResult {
pub strategy: String,
pub total_allocated: usize,
pub total_freed: usize,
pub collections: u64,
pub alloc_time_ns: u64,
pub gc_time_ns: u64,
}
impl GcBenchmarkResult {
pub fn alloc_throughput(&self) -> f64 {
if self.alloc_time_ns == 0 {
0.0
} else {
self.total_allocated as f64 / self.alloc_time_ns as f64
}
}
pub fn gc_overhead(&self) -> f64 {
let total = self.alloc_time_ns + self.gc_time_ns;
if total == 0 {
0.0
} else {
self.gc_time_ns as f64 / total as f64
}
}
}
#[derive(Debug, Clone, Default)]
pub struct GcStats {
pub collections: u64,
pub bytes_collected: u64,
pub pause_time_ns: u64,
pub heap_size: usize,
}
impl GcStats {
pub fn new() -> Self {
Self::default()
}
pub fn record_collection(&mut self, bytes: u64, pause_ns: u64) {
self.collections += 1;
self.bytes_collected += bytes;
self.pause_time_ns += pause_ns;
}
pub fn throughput_pct(&self, total_ns: u64) -> f64 {
if total_ns == 0 {
return 100.0;
}
let pause = self.pause_time_ns.min(total_ns);
(total_ns - pause) as f64 / total_ns as f64 * 100.0
}
}
#[derive(Clone, Debug)]
pub struct GcCycleRecord {
pub cycle_id: u64,
pub strategy: String,
pub bytes_before: usize,
pub bytes_after: usize,
pub bytes_freed: usize,
pub duration_ns: u64,
pub is_major: bool,
}
impl GcCycleRecord {
pub fn new(cycle_id: u64, strategy: &str, bytes_before: usize) -> Self {
Self {
cycle_id,
strategy: strategy.to_string(),
bytes_before,
bytes_after: 0,
bytes_freed: 0,
duration_ns: 0,
is_major: false,
}
}
pub fn finalize(&mut self, bytes_after: usize, duration_ns: u64) {
self.bytes_after = bytes_after;
self.bytes_freed = self.bytes_before.saturating_sub(bytes_after);
self.duration_ns = duration_ns;
}
pub fn survival_rate(&self) -> f64 {
if self.bytes_before == 0 {
1.0
} else {
self.bytes_after as f64 / self.bytes_before as f64
}
}
pub fn reclaim_rate(&self) -> f64 {
if self.bytes_before == 0 {
0.0
} else {
self.bytes_freed as f64 / self.bytes_before as f64
}
}
}
#[derive(Clone, Debug, Default)]
pub struct FinalizerQueue {
pub pending: Vec<u64>,
pub finalized_count: u64,
}
impl FinalizerQueue {
pub fn new() -> Self {
Self::default()
}
pub fn enqueue(&mut self, id: u64) {
self.pending.push(id);
}
pub fn drain_one(&mut self) -> Option<u64> {
if self.pending.is_empty() {
None
} else {
let id = self.pending.remove(0);
self.finalized_count += 1;
Some(id)
}
}
pub fn drain_all(&mut self) -> usize {
let count = self.pending.len();
self.pending.clear();
self.finalized_count += count as u64;
count
}
pub fn pending_count(&self) -> usize {
self.pending.len()
}
pub fn has_pending(&self) -> bool {
!self.pending.is_empty()
}
}
#[derive(Clone, Debug)]
pub struct GcTuner {
recent_pauses: Vec<u64>,
pub target_pause_ns: u64,
pub collection_threshold: f64,
pub enabled: bool,
pub window_size: usize,
}
impl GcTuner {
pub fn new() -> Self {
Self {
recent_pauses: Vec::new(),
target_pause_ns: 1_000_000,
collection_threshold: 0.75,
enabled: true,
window_size: 20,
}
}
pub fn record_pause(&mut self, pause_ns: u64) {
if self.recent_pauses.len() >= self.window_size {
self.recent_pauses.remove(0);
}
self.recent_pauses.push(pause_ns);
if self.enabled {
self.tune();
}
}
fn tune(&mut self) {
if self.recent_pauses.is_empty() {
return;
}
let avg: f64 =
self.recent_pauses.iter().sum::<u64>() as f64 / self.recent_pauses.len() as f64;
if avg > self.target_pause_ns as f64 * 1.5 {
self.collection_threshold = (self.collection_threshold - 0.05).max(0.5);
} else if avg < self.target_pause_ns as f64 * 0.5 {
self.collection_threshold = (self.collection_threshold + 0.05).min(0.95);
}
}
pub fn avg_pause_ns(&self) -> f64 {
if self.recent_pauses.is_empty() {
0.0
} else {
self.recent_pauses.iter().sum::<u64>() as f64 / self.recent_pauses.len() as f64
}
}
pub fn has_data(&self) -> bool {
self.recent_pauses.len() >= 3
}
}
pub struct SemispaceGc {
pub from_space: Vec<u8>,
pub to_space: Vec<u8>,
pub free_ptr: usize,
pub stats: GcStats,
}
impl SemispaceGc {
pub fn new(space_size: usize) -> Self {
Self {
from_space: vec![0u8; space_size],
to_space: vec![0u8; space_size],
free_ptr: 0,
stats: GcStats::new(),
}
}
pub fn allocate(&mut self, size: usize) -> Option<usize> {
if self.free_ptr + size > self.from_space.len() {
return None;
}
let offset = self.free_ptr;
self.free_ptr += size;
self.stats.heap_size = self.free_ptr;
Some(offset)
}
pub fn flip(&mut self) -> usize {
let surviving = self.free_ptr;
let len = surviving.min(self.to_space.len());
self.to_space[..len].copy_from_slice(&self.from_space[..len]);
std::mem::swap(&mut self.from_space, &mut self.to_space);
for b in self.to_space.iter_mut() {
*b = 0;
}
let freed = (self.from_space.len().saturating_sub(surviving)) as u64;
self.stats.record_collection(freed, 0);
self.free_ptr = surviving;
self.stats.heap_size = self.free_ptr;
surviving
}
pub fn stats(&self) -> &GcStats {
&self.stats
}
}
#[derive(Clone, Debug, Default)]
pub struct GcRootSet {
roots: Vec<usize>,
named: Vec<(String, usize)>,
}
impl GcRootSet {
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, addr: usize) {
if !self.roots.contains(&addr) {
self.roots.push(addr);
}
}
pub fn add_named(&mut self, name: impl Into<String>, addr: usize) {
let name = name.into();
self.named.retain(|(n, _)| n != &name);
self.named.push((name, addr));
self.add(addr);
}
pub fn remove(&mut self, addr: usize) {
self.roots.retain(|&r| r != addr);
self.named.retain(|(_, a)| *a != addr);
}
pub fn all_roots(&self) -> &[usize] {
&self.roots
}
pub fn lookup_named(&self, name: &str) -> Option<usize> {
self.named.iter().find(|(n, _)| n == name).map(|(_, a)| *a)
}
pub fn len(&self) -> usize {
self.roots.len()
}
pub fn is_empty(&self) -> bool {
self.roots.is_empty()
}
pub fn clear(&mut self) {
self.roots.clear();
self.named.clear();
}
}
#[derive(Clone, Debug)]
pub struct RegionBasedGc {
pub regions: Vec<GcRegion>,
pub region_size: usize,
pub current_region: usize,
pub stats: GcStats,
}
impl RegionBasedGc {
pub fn new(num_regions: usize, region_size: usize) -> Self {
let regions = (0..num_regions)
.map(|i| GcRegion::new(i, region_size))
.collect();
Self {
regions,
region_size,
current_region: 0,
stats: GcStats::new(),
}
}
pub fn allocate(&mut self, size: usize) -> Option<(usize, usize)> {
for start in self.current_region..self.regions.len() {
if let Some(off) = self.regions[start].allocate(size) {
self.stats.heap_size += size;
return Some((start, off));
}
}
None
}
pub fn select_cset(&mut self, max_regions: usize) {
for r in self.regions.iter_mut() {
r.in_cset = false;
}
let mut candidates: Vec<usize> = (0..self.regions.len())
.filter(|&i| !self.regions[i].is_empty())
.collect();
candidates.sort_by_key(|&i| self.regions[i].liveness_pct);
for &idx in candidates.iter().take(max_regions) {
self.regions[idx].in_cset = true;
}
}
pub fn collect_cset(&mut self) -> usize {
let mut freed = 0usize;
for region in self.regions.iter_mut() {
if region.in_cset {
let dead = region.used * (100 - region.liveness_pct as usize) / 100;
freed += dead;
region.used = region.used.saturating_sub(dead);
self.stats.heap_size = self.stats.heap_size.saturating_sub(dead);
region.in_cset = false;
}
}
self.stats.record_collection(freed as u64, 0);
freed
}
pub fn total_used(&self) -> usize {
self.regions.iter().map(|r| r.used).sum()
}
pub fn total_free(&self) -> usize {
self.regions.iter().map(|r| r.free()).sum()
}
pub fn empty_region_count(&self) -> usize {
self.regions.iter().filter(|r| r.is_empty()).count()
}
pub fn full_region_count(&self) -> usize {
self.regions.iter().filter(|r| r.is_nearly_full()).count()
}
pub fn avg_liveness(&self) -> f64 {
let non_empty: Vec<&GcRegion> = self.regions.iter().filter(|r| !r.is_empty()).collect();
if non_empty.is_empty() {
return 100.0;
}
non_empty.iter().map(|r| r.liveness_pct as f64).sum::<f64>() / non_empty.len() as f64
}
}
pub struct IncrementalGc {
pub cells: Vec<HeapCell>,
pub roots: Vec<usize>,
pub gray_set: Vec<usize>,
pub stats: GcStats,
pub collecting: bool,
pub steps_per_cycle: usize,
}
impl IncrementalGc {
pub fn new() -> Self {
Self {
cells: Vec::new(),
roots: Vec::new(),
gray_set: Vec::new(),
stats: GcStats::new(),
collecting: false,
steps_per_cycle: 10,
}
}
pub fn allocate(&mut self, size: usize) -> usize {
let idx = self.cells.len();
self.cells.push(HeapCell::new(size));
self.stats.heap_size += size;
idx
}
pub fn add_root(&mut self, idx: usize) {
if !self.roots.contains(&idx) {
self.roots.push(idx);
}
}
pub fn remove_root(&mut self, idx: usize) {
self.roots.retain(|&r| r != idx);
}
pub fn write_barrier(&mut self, from: usize, to: usize) {
if from < self.cells.len() {
self.cells[from].add_child(to);
if self.cells[from].color == TriColor::Black
&& to < self.cells.len()
&& self.cells[to].color == TriColor::White
{
self.cells[from].color = TriColor::Gray;
if !self.gray_set.contains(&from) {
self.gray_set.push(from);
}
}
}
}
pub fn begin_collection(&mut self) {
for cell in self.cells.iter_mut() {
cell.color = TriColor::White;
}
self.gray_set.clear();
for &root in &self.roots.clone() {
if root < self.cells.len() {
self.cells[root].color = TriColor::Gray;
self.gray_set.push(root);
}
}
self.collecting = true;
}
pub fn step_mark(&mut self) -> bool {
if let Some(idx) = self.gray_set.pop() {
if idx < self.cells.len() {
self.cells[idx].color = TriColor::Black;
let children: Vec<usize> = self.cells[idx].children.clone();
for child in children {
if child < self.cells.len() && self.cells[child].color == TriColor::White {
self.cells[child].color = TriColor::Gray;
self.gray_set.push(child);
}
}
}
}
self.gray_set.is_empty()
}
pub fn mark_all(&mut self) {
while !self.step_mark() {}
}
pub fn sweep(&mut self) -> usize {
let mut freed = 0usize;
for cell in self.cells.iter_mut() {
if cell.color == TriColor::White && cell.live {
freed += cell.size;
self.stats.heap_size = self.stats.heap_size.saturating_sub(cell.size);
cell.live = false;
}
}
self.stats.record_collection(freed as u64, 0);
self.collecting = false;
freed
}
pub fn collect(&mut self) -> usize {
self.begin_collection();
self.mark_all();
self.sweep()
}
pub fn live_count(&self) -> usize {
self.cells.iter().filter(|c| c.live).count()
}
pub fn live_bytes(&self) -> usize {
self.cells.iter().filter(|c| c.live).map(|c| c.size).sum()
}
pub fn gray_count(&self) -> usize {
self.gray_set.len()
}
}
#[derive(Clone, Debug)]
pub struct CardTable {
pub cards: Vec<bool>,
pub card_size: usize,
pub heap_size: usize,
}
impl CardTable {
pub fn new(heap_size: usize, card_size: usize) -> Self {
let num_cards = (heap_size + card_size - 1) / card_size;
Self {
cards: vec![false; num_cards],
card_size,
heap_size,
}
}
pub fn mark_dirty(&mut self, addr: usize) {
let card = addr / self.card_size;
if card < self.cards.len() {
self.cards[card] = true;
}
}
pub fn clear(&mut self) {
for bit in self.cards.iter_mut() {
*bit = false;
}
}
pub fn dirty_cards(&self) -> Vec<usize> {
self.cards
.iter()
.enumerate()
.filter(|(_, &dirty)| dirty)
.map(|(i, _)| i)
.collect()
}
pub fn dirty_count(&self) -> usize {
self.cards.iter().filter(|&&b| b).count()
}
pub fn total_cards(&self) -> usize {
self.cards.len()
}
pub fn is_dirty(&self, addr: usize) -> bool {
let card = addr / self.card_size;
card < self.cards.len() && self.cards[card]
}
pub fn card_range(&self, idx: usize) -> (usize, usize) {
let start = idx * self.card_size;
let end = (start + self.card_size).min(self.heap_size);
(start, end)
}
}
#[derive(Clone, Debug)]
pub struct HeapCell {
pub color: TriColor,
pub children: Vec<usize>,
pub live: bool,
pub size: usize,
}
impl HeapCell {
pub fn new(size: usize) -> Self {
Self {
color: TriColor::White,
children: Vec::new(),
live: true,
size,
}
}
pub fn add_child(&mut self, idx: usize) {
if !self.children.contains(&idx) {
self.children.push(idx);
}
}
}
#[derive(Clone, Debug, Default)]
pub struct StickyMarkBitsGc {
pub bits: Vec<bool>,
pub watermark: usize,
pub stats: GcStats,
pub capacity: usize,
}
impl StickyMarkBitsGc {
pub fn new(capacity: usize) -> Self {
Self {
bits: vec![false; capacity],
watermark: 0,
stats: GcStats::new(),
capacity,
}
}
pub fn allocate(&mut self, size: usize) -> Option<usize> {
let offset = self.watermark;
if offset + size > self.capacity {
return None;
}
self.watermark += size;
self.stats.heap_size = self.watermark;
Some(offset)
}
pub fn mark_sticky(&mut self, addr: usize) {
if addr < self.bits.len() {
self.bits[addr] = true;
}
}
pub fn collect(&mut self) -> usize {
let mut last_sticky = 0usize;
for (i, &bit) in self.bits.iter().enumerate() {
if bit {
last_sticky = i + 1;
}
}
let freed = self.watermark.saturating_sub(last_sticky);
self.watermark = last_sticky;
self.stats.heap_size = self.watermark;
self.stats.record_collection(freed as u64, 0);
freed
}
pub fn promote_all(&mut self) {
for bit in self.bits.iter_mut() {
*bit = true;
}
}
pub fn reset_bits(&mut self) {
for bit in self.bits.iter_mut() {
*bit = false;
}
}
pub fn sticky_bytes(&self) -> usize {
self.bits.iter().filter(|&&b| b).count()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CompactionStrategy {
Never,
Always,
OnFragmentation,
MajorOnly,
}
impl CompactionStrategy {
pub fn name(self) -> &'static str {
match self {
CompactionStrategy::Never => "never",
CompactionStrategy::Always => "always",
CompactionStrategy::OnFragmentation => "on-fragmentation",
CompactionStrategy::MajorOnly => "major-only",
}
}
pub fn is_enabled(self) -> bool {
!matches!(self, CompactionStrategy::Never)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TriColor {
White,
Gray,
Black,
}
#[derive(Clone, Debug)]
pub struct GcConfig {
pub strategy: GcStrategy,
pub heap_limit: usize,
pub collection_threshold: f64,
pub incremental_steps: usize,
pub write_barriers: bool,
pub young_gen_size: usize,
pub old_gen_size: usize,
}
impl GcConfig {
pub fn new(strategy: GcStrategy) -> Self {
Self {
strategy,
..Default::default()
}
}
pub fn with_heap_limit(mut self, bytes: usize) -> Self {
self.heap_limit = bytes;
self
}
pub fn with_threshold(mut self, threshold: f64) -> Self {
self.collection_threshold = threshold.clamp(0.1, 1.0);
self
}
pub fn with_incremental_steps(mut self, steps: usize) -> Self {
self.incremental_steps = steps;
self
}
pub fn with_write_barriers(mut self, enabled: bool) -> Self {
self.write_barriers = enabled;
self
}
pub fn validate(&self) -> Vec<String> {
let mut issues = Vec::new();
if self.heap_limit == 0 {
issues.push("heap_limit must be > 0".to_string());
}
if self.collection_threshold <= 0.0 || self.collection_threshold > 1.0 {
issues.push("collection_threshold must be in (0.0, 1.0]".to_string());
}
if self.incremental_steps == 0 {
issues.push("incremental_steps must be > 0".to_string());
}
if self.young_gen_size >= self.old_gen_size {
issues.push("young_gen_size must be < old_gen_size".to_string());
}
issues
}
}
pub enum ActiveGc {
MarkSweep(MarkSweepGc),
Semispace(SemispaceGc),
Generational(GenerationalGc),
Incremental(IncrementalGc),
}
#[derive(Clone, Debug, Default)]
pub struct HeapFragmentation {
pub total_capacity: usize,
pub live_bytes: usize,
pub largest_free_block: usize,
pub free_block_count: usize,
}
impl HeapFragmentation {
pub fn new(total_capacity: usize, live_bytes: usize) -> Self {
Self {
total_capacity,
live_bytes,
largest_free_block: 0,
free_block_count: 0,
}
}
pub fn ratio(&self) -> f64 {
let free = self.total_capacity.saturating_sub(self.live_bytes);
if free == 0 || self.largest_free_block == 0 {
return 0.0;
}
1.0 - (self.largest_free_block as f64 / free as f64)
}
pub fn is_high(&self) -> bool {
self.ratio() > 0.5
}
pub fn free_bytes(&self) -> usize {
self.total_capacity.saturating_sub(self.live_bytes)
}
pub fn utilization(&self) -> f64 {
if self.total_capacity == 0 {
0.0
} else {
self.live_bytes as f64 / self.total_capacity as f64
}
}
}
#[derive(Clone, Debug)]
pub struct GcRegion {
pub id: usize,
pub used: usize,
pub capacity: usize,
pub in_cset: bool,
pub humongous: bool,
pub liveness_pct: u8,
}
impl GcRegion {
pub fn new(id: usize, capacity: usize) -> Self {
Self {
id,
used: 0,
capacity,
in_cset: false,
humongous: false,
liveness_pct: 100,
}
}
pub fn free(&self) -> usize {
self.capacity.saturating_sub(self.used)
}
pub fn allocate(&mut self, size: usize) -> Option<usize> {
if self.used + size > self.capacity {
return None;
}
let off = self.used;
self.used += size;
Some(off)
}
pub fn add_to_cset(&mut self) {
self.in_cset = true;
}
pub fn set_liveness(&mut self, pct: u8) {
self.liveness_pct = pct.min(100);
}
pub fn is_nearly_full(&self) -> bool {
self.used * 10 >= self.capacity * 9
}
pub fn is_empty(&self) -> bool {
self.used == 0
}
}
#[derive(Clone, Debug, Default)]
pub struct ObjectAgeTable {
pub ages: std::collections::HashMap<u64, u32>,
pub promoted_count: u64,
pub promotion_threshold: u32,
}
impl ObjectAgeTable {
pub fn new(promotion_threshold: u32) -> Self {
Self {
ages: std::collections::HashMap::new(),
promoted_count: 0,
promotion_threshold,
}
}
pub fn register(&mut self, id: u64) {
self.ages.insert(id, 0);
}
pub fn age_object(&mut self, id: u64) -> bool {
if let Some(age) = self.ages.get_mut(&id) {
*age += 1;
if *age >= self.promotion_threshold {
self.promoted_count += 1;
return true;
}
}
false
}
pub fn remove(&mut self, id: u64) {
self.ages.remove(&id);
}
pub fn age_of(&self, id: u64) -> Option<u32> {
self.ages.get(&id).copied()
}
pub fn len(&self) -> usize {
self.ages.len()
}
pub fn is_empty(&self) -> bool {
self.ages.is_empty()
}
pub fn avg_age(&self) -> f64 {
if self.ages.is_empty() {
0.0
} else {
self.ages.values().sum::<u32>() as f64 / self.ages.len() as f64
}
}
pub fn old_objects(&self) -> Vec<u64> {
self.ages
.iter()
.filter(|(_, &age)| age >= self.promotion_threshold)
.map(|(&id, _)| id)
.collect()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum GcStrategy {
RefCounting,
MarkSweep,
Semispace,
Generational,
Incremental,
}
impl GcStrategy {
pub fn name(&self) -> &str {
match self {
GcStrategy::RefCounting => "RefCounting",
GcStrategy::MarkSweep => "MarkSweep",
GcStrategy::Semispace => "Semispace",
GcStrategy::Generational => "Generational",
GcStrategy::Incremental => "Incremental",
}
}
pub fn description(&self) -> &str {
match self {
GcStrategy::RefCounting => "Reference counting with cycle detection",
GcStrategy::MarkSweep => "Classic mark-and-sweep stop-the-world collector",
GcStrategy::Semispace => "Copying collector using two semispaces",
GcStrategy::Generational => "Generational collector with young/old generations",
GcStrategy::Incremental => "Incremental tri-color mark-and-sweep collector",
}
}
pub fn is_concurrent(&self) -> bool {
matches!(self, GcStrategy::Incremental)
}
}
pub struct GenerationalGc {
pub young: SemispaceGc,
pub old: MarkSweepGc,
pub promotion_threshold: usize,
}
impl GenerationalGc {
pub fn new() -> Self {
Self {
young: SemispaceGc::new(64 * 1024),
old: MarkSweepGc::new(512 * 1024),
promotion_threshold: 2,
}
}
pub fn minor_gc(&mut self) -> usize {
let surviving = self.young.flip();
if surviving > 0 {
if let Some(off) = self.old.allocate(surviving) {
let len = surviving.min(self.young.from_space.len());
let src: Vec<u8> = self.young.from_space[..len].to_vec();
let dst_len = self.old.heap.len();
let copy_len = len.min(dst_len.saturating_sub(off));
self.old.heap[off..off + copy_len].copy_from_slice(&src[..copy_len]);
}
}
self.young.stats.bytes_collected as usize
}
pub fn major_gc(&mut self) -> usize {
self.old.collect()
}
pub fn stats_report(&self) -> String {
format!(
"Young: {} collections, {} bytes collected | Old: {} collections, {} bytes collected",
self.young.stats.collections,
self.young.stats.bytes_collected,
self.old.stats.collections,
self.old.stats.bytes_collected,
)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GcPhase {
Idle,
InitialMark,
ConcurrentMark,
Remark,
Sweep,
Compact,
Finalize,
}
impl GcPhase {
pub fn is_stop_the_world(self) -> bool {
matches!(
self,
GcPhase::InitialMark | GcPhase::Remark | GcPhase::Compact
)
}
pub fn name(self) -> &'static str {
match self {
GcPhase::Idle => "idle",
GcPhase::InitialMark => "initial-mark",
GcPhase::ConcurrentMark => "concurrent-mark",
GcPhase::Remark => "remark",
GcPhase::Sweep => "sweep",
GcPhase::Compact => "compact",
GcPhase::Finalize => "finalize",
}
}
}
#[derive(Clone, Debug, Default)]
pub struct GcBenchmark {
pub results: std::collections::HashMap<String, GcBenchmarkResult>,
}
impl GcBenchmark {
pub fn new() -> Self {
Self::default()
}
pub fn run_mark_sweep(&mut self, num_allocs: usize, alloc_size: usize) {
let mut gc = MarkSweepGc::new(num_allocs * alloc_size * 2);
let mut total_freed = 0usize;
for i in 0..num_allocs {
if gc.allocate(alloc_size).is_none() {
total_freed += gc.collect();
let _ = gc.allocate(alloc_size);
}
if i % 2 == 0 {
let start = i * alloc_size;
if start < gc.mark_bits.len() {
gc.mark(start);
}
}
}
let result = GcBenchmarkResult {
strategy: "MarkSweep".to_string(),
total_allocated: num_allocs * alloc_size,
total_freed,
collections: gc.stats.collections,
alloc_time_ns: 0,
gc_time_ns: gc.stats.pause_time_ns,
};
self.results.insert("MarkSweep".to_string(), result);
}
pub fn run_semispace(&mut self, num_allocs: usize, alloc_size: usize) {
let mut gc = SemispaceGc::new(num_allocs * alloc_size);
let mut total_freed = 0usize;
for _ in 0..num_allocs {
if gc.allocate(alloc_size).is_none() {
let surviving = gc.flip();
total_freed += gc.from_space.len().saturating_sub(surviving);
}
}
let result = GcBenchmarkResult {
strategy: "Semispace".to_string(),
total_allocated: num_allocs * alloc_size,
total_freed,
collections: gc.stats.collections,
alloc_time_ns: 0,
gc_time_ns: gc.stats.pause_time_ns,
};
self.results.insert("Semispace".to_string(), result);
}
pub fn print_comparison(&self) -> String {
let mut lines = vec!["=== GC Benchmark Comparison ===".to_string()];
for (name, result) in &self.results {
lines.push(format!(
"{}: allocated={}, freed={}, collections={}",
name, result.total_allocated, result.total_freed, result.collections
));
}
lines.join("\n")
}
}
pub struct MarkSweepGc {
pub heap: Vec<u8>,
pub mark_bits: Vec<bool>,
pub stats: GcStats,
pub heap_limit: usize,
}
impl MarkSweepGc {
pub fn new(heap_limit: usize) -> Self {
Self {
heap: Vec::new(),
mark_bits: Vec::new(),
stats: GcStats::new(),
heap_limit,
}
}
pub fn allocate(&mut self, size: usize) -> Option<usize> {
let offset = self.heap.len();
if offset + size > self.heap_limit {
return None;
}
self.heap.extend(std::iter::repeat(0u8).take(size));
self.mark_bits.extend(std::iter::repeat(false).take(size));
self.stats.heap_size = self.heap.len();
Some(offset)
}
pub fn mark(&mut self, addr: usize) {
if addr < self.mark_bits.len() {
self.mark_bits[addr] = true;
}
}
pub fn sweep(&mut self) -> usize {
let mut freed = 0usize;
for (bit, byte) in self.mark_bits.iter_mut().zip(self.heap.iter_mut()) {
if !*bit {
*byte = 0;
freed += 1;
}
*bit = false;
}
self.stats.heap_size = self.heap.len();
freed
}
pub fn collect(&mut self) -> usize {
let freed = self.sweep() as u64;
self.stats.record_collection(freed, 0);
freed as usize
}
pub fn stats(&self) -> &GcStats {
&self.stats
}
}
#[derive(Clone, Debug, Default)]
pub struct GcPauseLog {
pub pauses: Vec<(u64, u64)>,
}
impl GcPauseLog {
pub fn new() -> Self {
Self::default()
}
pub fn record(&mut self, start_ns: u64, duration_ns: u64) {
self.pauses.push((start_ns, duration_ns));
}
pub fn total_pause_ns(&self) -> u64 {
self.pauses.iter().map(|(_, d)| d).sum()
}
pub fn max_pause_ns(&self) -> u64 {
self.pauses.iter().map(|(_, d)| *d).max().unwrap_or(0)
}
pub fn avg_pause_ns(&self) -> f64 {
if self.pauses.is_empty() {
0.0
} else {
self.total_pause_ns() as f64 / self.pauses.len() as f64
}
}
pub fn count(&self) -> usize {
self.pauses.len()
}
pub fn p99_pause_ns(&self) -> u64 {
if self.pauses.is_empty() {
return 0;
}
let mut durations: Vec<u64> = self.pauses.iter().map(|(_, d)| *d).collect();
durations.sort_unstable();
let idx = ((durations.len() as f64 * 0.99) as usize).min(durations.len() - 1);
durations[idx]
}
}
pub struct GcHandle {
pub gc: ActiveGc,
pub config: GcConfig,
}
impl GcHandle {
pub fn from_config(config: GcConfig) -> Self {
let gc = match config.strategy {
GcStrategy::RefCounting | GcStrategy::MarkSweep => {
ActiveGc::MarkSweep(MarkSweepGc::new(config.heap_limit))
}
GcStrategy::Semispace => ActiveGc::Semispace(SemispaceGc::new(config.heap_limit / 2)),
GcStrategy::Generational => ActiveGc::Generational(GenerationalGc::new()),
GcStrategy::Incremental => ActiveGc::Incremental(IncrementalGc::new()),
};
Self { gc, config }
}
pub fn heap_size(&self) -> usize {
match &self.gc {
ActiveGc::MarkSweep(g) => g.stats.heap_size,
ActiveGc::Semispace(g) => g.stats.heap_size,
ActiveGc::Generational(g) => g.young.stats.heap_size + g.old.stats.heap_size,
ActiveGc::Incremental(g) => g.stats.heap_size,
}
}
pub fn should_collect(&self) -> bool {
let used = self.heap_size();
let limit = self.config.heap_limit;
if limit == 0 {
return false;
}
used as f64 / limit as f64 >= self.config.collection_threshold
}
pub fn strategy_name(&self) -> &str {
self.config.strategy.name()
}
pub fn stats(&self) -> GcStats {
match &self.gc {
ActiveGc::MarkSweep(g) => g.stats.clone(),
ActiveGc::Semispace(g) => g.stats.clone(),
ActiveGc::Generational(g) => {
let mut s = g.young.stats.clone();
s.collections += g.old.stats.collections;
s.bytes_collected += g.old.stats.bytes_collected;
s.pause_time_ns += g.old.stats.pause_time_ns;
s
}
ActiveGc::Incremental(g) => g.stats.clone(),
}
}
}
pub struct WriteBarrier {
pub log: Vec<(usize, usize, BarrierAction)>,
pub active: bool,
}
impl WriteBarrier {
pub fn new() -> Self {
Self {
log: Vec::new(),
active: false,
}
}
pub fn activate(&mut self) {
self.active = true;
}
pub fn deactivate(&mut self) {
self.active = false;
}
pub fn record(&mut self, src: usize, dst: usize, action: BarrierAction) {
if self.active {
self.log.push((src, dst, action));
}
}
pub fn drain(&mut self) -> Vec<(usize, usize, BarrierAction)> {
std::mem::take(&mut self.log)
}
pub fn pending_count(&self) -> usize {
self.log.len()
}
}
#[derive(Clone, Debug, Default)]
pub struct GcSafePoint {
pub stop_requested: bool,
pub threads_at_safepoint: u32,
pub total_threads: u32,
}
impl GcSafePoint {
pub fn new(total_threads: u32) -> Self {
Self {
stop_requested: false,
threads_at_safepoint: 0,
total_threads,
}
}
pub fn request_stop(&mut self) {
self.stop_requested = true;
}
pub fn thread_at_safepoint(&mut self) {
if self.threads_at_safepoint < self.total_threads {
self.threads_at_safepoint += 1;
}
}
pub fn thread_exit_safepoint(&mut self) {
if self.threads_at_safepoint > 0 {
self.threads_at_safepoint -= 1;
}
}
pub fn all_stopped(&self) -> bool {
self.stop_requested && self.threads_at_safepoint >= self.total_threads
}
pub fn release(&mut self) {
self.stop_requested = false;
self.threads_at_safepoint = 0;
}
pub fn stopped_fraction(&self) -> f64 {
if self.total_threads == 0 {
0.0
} else {
self.threads_at_safepoint as f64 / self.total_threads as f64
}
}
}
#[derive(Clone, Debug)]
pub struct GcObjectHeader {
pub id: u64,
pub size: usize,
pub ref_count: u32,
pub generation: u8,
pub pinned: bool,
pub has_finalizer: bool,
pub marked: bool,
pub forwarding: Option<u64>,
}
impl GcObjectHeader {
pub fn new(id: u64, size: usize) -> Self {
Self {
id,
size,
ref_count: 1,
generation: 0,
pinned: false,
has_finalizer: false,
marked: false,
forwarding: None,
}
}
pub fn inc_ref(&mut self) {
self.ref_count = self.ref_count.saturating_add(1);
}
pub fn dec_ref(&mut self) -> bool {
if self.ref_count > 0 {
self.ref_count -= 1;
}
self.ref_count == 0
}
pub fn mark(&mut self) {
self.marked = true;
}
pub fn clear_mark(&mut self) {
self.marked = false;
}
pub fn promote(&mut self) {
self.generation = self.generation.saturating_add(1);
}
pub fn pin(&mut self) {
self.pinned = true;
}
pub fn unpin(&mut self) {
self.pinned = false;
}
pub fn forward_to(&mut self, addr: u64) {
self.forwarding = Some(addr);
}
pub fn is_forwarded(&self) -> bool {
self.forwarding.is_some()
}
}