use std::collections::{HashMap, HashSet};
use std::sync::{Arc, RwLock, Weak};
use std::time::Instant;
pub type GenerationId = u32;
pub const NURSERY_GENERATION: GenerationId = 0;
pub const MAX_GENERATIONS: GenerationId = 3;
pub trait GcObject: Send + Sync + std::any::Any + std::fmt::Debug {
fn generation(&self) -> GenerationId;
fn set_generation(&mut self, generation: GenerationId);
fn references(&self) -> Vec<GcPtr>;
fn mark(&self);
fn is_marked(&self) -> bool;
fn clear_mark(&self);
fn size_hint(&self) -> usize;
}
#[derive(Debug, Clone)]
pub struct GcPtr {
inner: Arc<dyn GcObject>,
id: ObjectId,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ObjectId(u64);
impl ObjectId {
pub fn new(id: u64) -> Self {
ObjectId(id)
}
pub fn raw(&self) -> u64 {
self.0
}
}
#[derive(Debug, Clone)]
pub struct WeakGcPtr {
inner: Weak<dyn GcObject>,
id: ObjectId,
}
#[derive(Debug, Clone, Default)]
pub struct GcStats {
pub generation: GenerationId,
pub objects_before: usize,
pub objects_after: usize,
pub objects_promoted: usize,
pub collection_time_us: u64,
pub memory_freed: usize,
pub memory_in_use: usize,
}
#[derive(Debug, Clone)]
pub struct GcConfig {
pub nursery_threshold: usize,
pub gen1_threshold: usize,
pub gen2_threshold: usize,
pub max_promotions: usize,
pub concurrent_collection: bool,
}
impl Default for GcConfig {
fn default() -> Self {
Self {
nursery_threshold: 1024 * 1024, gen1_threshold: 8 * 1024 * 1024, gen2_threshold: 32 * 1024 * 1024, max_promotions: 1000,
concurrent_collection: false,
}
}
}
pub struct GenerationalGc {
generations: Vec<RwLock<HashSet<ObjectId>>>,
objects: RwLock<HashMap<ObjectId, GcPtr>>,
weak_refs: RwLock<HashMap<ObjectId, Vec<WeakGcPtr>>>,
roots: RwLock<HashSet<ObjectId>>,
config: GcConfig,
next_id: std::sync::atomic::AtomicU64,
stats: RwLock<Vec<GcStats>>,
generation_sizes: RwLock<Vec<usize>>,
}
impl GenerationalGc {
pub fn new(config: GcConfig) -> Self {
let mut generations = Vec::new();
let mut generation_sizes = Vec::new();
for _ in 0..=MAX_GENERATIONS {
generations.push(RwLock::new(HashSet::new()));
generation_sizes.push(0);
}
Self {
generations,
objects: RwLock::new(HashMap::new()),
weak_refs: RwLock::new(HashMap::new()),
roots: RwLock::new(HashSet::new()),
config,
next_id: std::sync::atomic::AtomicU64::new(1),
stats: RwLock::new(Vec::new()),
generation_sizes: RwLock::new(generation_sizes),
}
}
pub fn alloc<T: GcObject + 'static>(&self, mut obj: T) -> GcPtr {
let id = ObjectId(self.next_id.fetch_add(1, std::sync::atomic::Ordering::SeqCst));
obj.set_generation(NURSERY_GENERATION);
let gc_ptr = GcPtr {
inner: Arc::new(obj),
id,
};
if let Ok(mut nursery) = self.generations[NURSERY_GENERATION as usize].write() {
nursery.insert(id);
}
if let Ok(mut objects) = self.objects.write() {
objects.insert(id, gc_ptr.clone());
}
if let Ok(mut sizes) = self.generation_sizes.write() {
sizes[NURSERY_GENERATION as usize] += gc_ptr.inner.size_hint();
}
self.maybe_collect();
gc_ptr
}
pub fn add_root(&self, ptr: &GcPtr) {
if let Ok(mut roots) = self.roots.write() {
roots.insert(ptr.id);
}
}
pub fn remove_root(&self, ptr: &GcPtr) {
if let Ok(mut roots) = self.roots.write() {
roots.remove(&ptr.id);
}
}
pub fn downgrade(&self, ptr: &GcPtr) -> WeakGcPtr {
let weak_ptr = WeakGcPtr {
inner: Arc::downgrade(&ptr.inner),
id: ptr.id,
};
if let Ok(mut weak_refs) = self.weak_refs.write() {
weak_refs.entry(ptr.id).or_default().push(weak_ptr.clone());
}
weak_ptr
}
pub fn maybe_collect(&self) {
if let Ok(sizes) = self.generation_sizes.read() {
if sizes[NURSERY_GENERATION as usize] > self.config.nursery_threshold {
self.collect_generation(NURSERY_GENERATION);
return;
}
if sizes[1] > self.config.gen1_threshold {
self.collect_generation(1);
return;
}
if sizes[2] > self.config.gen2_threshold {
self.collect_generation(2);
}
}
}
pub fn collect_generation(&self, generation: GenerationId) {
let start_time = Instant::now();
let objects_before = if let Ok(generation_objects) = self.generations[generation as usize].read() {
generation_objects.len()
} else {
return;
};
self.mark_from_roots();
if generation < MAX_GENERATIONS {
self.mark_from_older_generations(generation);
}
let (objects_collected, memory_freed, objects_promoted) = self.sweep_generation(generation);
let collection_time_us = start_time.elapsed().as_micros() as u64;
let objects_after = objects_before - objects_collected;
let memory_in_use = if let Ok(sizes) = self.generation_sizes.read() {
sizes.iter().sum()
} else {
0
};
let stats = GcStats {
generation,
objects_before,
objects_after,
objects_promoted,
collection_time_us,
memory_freed,
memory_in_use,
};
if let Ok(mut all_stats) = self.stats.write() {
all_stats.push(stats);
if all_stats.len() > 100 {
all_stats.remove(0);
}
}
}
fn mark_from_roots(&self) {
if let (Ok(roots), Ok(objects)) = (self.roots.read(), self.objects.read()) {
for &root_id in roots.iter() {
if let Some(root_obj) = objects.get(&root_id) {
self.mark_object_and_references(root_obj);
}
}
}
}
fn mark_from_older_generations(&self, generation: GenerationId) {
if let Ok(objects) = self.objects.read() {
for gen_idx in (generation + 1)..=MAX_GENERATIONS {
if let Ok(gen_objects) = self.generations[gen_idx as usize].read() {
for &obj_id in gen_objects.iter() {
if let Some(obj) = objects.get(&obj_id) {
for reference in obj.inner.references() {
if let Some(ref_obj) = objects.get(&reference.id) {
if ref_obj.inner.generation() <= generation {
self.mark_object_and_references(ref_obj);
}
}
}
}
}
}
}
}
}
fn mark_object_and_references(&self, obj: &GcPtr) {
if obj.inner.is_marked() {
return; }
obj.inner.mark();
if let Ok(objects) = self.objects.read() {
for reference in obj.inner.references() {
if let Some(ref_obj) = objects.get(&reference.id) {
self.mark_object_and_references(ref_obj);
}
}
}
}
fn sweep_generation(&self, generation: GenerationId) -> (usize, usize, usize) {
let mut objects_collected = 0;
let mut memory_freed = 0;
let mut objects_promoted = 0;
let mut to_remove = Vec::new();
let mut to_promote = Vec::new();
if let (Ok(mut gen_objects), Ok(objects)) =
(self.generations[generation as usize].write(), self.objects.read()) {
for &obj_id in gen_objects.iter() {
if let Some(obj) = objects.get(&obj_id) {
if !obj.inner.is_marked() {
to_remove.push(obj_id);
memory_freed += obj.inner.size_hint();
objects_collected += 1;
} else {
if generation < MAX_GENERATIONS && objects_promoted < self.config.max_promotions {
to_promote.push(obj_id);
objects_promoted += 1;
}
obj.inner.clear_mark();
}
}
}
for obj_id in &to_remove {
gen_objects.remove(obj_id);
}
for obj_id in &to_promote {
gen_objects.remove(obj_id);
}
}
if generation < MAX_GENERATIONS && !to_promote.is_empty() {
if let (Ok(mut next_gen), Ok(mut objects)) =
(self.generations[(generation + 1) as usize].write(), self.objects.write()) {
for obj_id in to_promote {
next_gen.insert(obj_id);
if let Some(_obj) = objects.get_mut(&obj_id) {
}
}
}
}
if let Ok(mut objects) = self.objects.write() {
for obj_id in to_remove {
objects.remove(&obj_id);
}
}
if let Ok(mut sizes) = self.generation_sizes.write() {
sizes[generation as usize] = sizes[generation as usize].saturating_sub(memory_freed);
}
(objects_collected, memory_freed, objects_promoted)
}
pub fn collect_all(&self) {
for generation in 0..=MAX_GENERATIONS {
self.collect_generation(generation);
}
}
pub fn get_stats(&self) -> Vec<GcStats> {
if let Ok(stats) = self.stats.read() {
stats.clone()
} else {
Vec::new()
}
}
pub fn memory_usage(&self) -> Vec<usize> {
if let Ok(sizes) = self.generation_sizes.read() {
sizes.clone()
} else {
vec![0; (MAX_GENERATIONS + 1) as usize]
}
}
pub fn object_count(&self) -> usize {
if let Ok(objects) = self.objects.read() {
objects.len()
} else {
0
}
}
pub fn debug_info(&self) -> GcDebugInfo {
let mut generation_counts = Vec::new();
for generation in &self.generations {
if let Ok(gen_objects) = generation.read() {
generation_counts.push(gen_objects.len());
} else {
generation_counts.push(0);
}
}
let memory_usage = self.memory_usage();
let total_memory = memory_usage.iter().sum();
let root_count = if let Ok(roots) = self.roots.read() {
roots.len()
} else {
0
};
let weak_ref_count = if let Ok(weak_refs) = self.weak_refs.read() {
weak_refs.values().map(|v| v.len()).sum()
} else {
0
};
GcDebugInfo {
generation_counts,
memory_usage,
total_memory,
root_count,
weak_ref_count,
total_objects: self.object_count(),
}
}
}
#[derive(Debug, Clone)]
pub struct GcDebugInfo {
pub generation_counts: Vec<usize>,
pub memory_usage: Vec<usize>,
pub total_memory: usize,
pub root_count: usize,
pub weak_ref_count: usize,
pub total_objects: usize,
}
impl GcPtr {
pub fn id(&self) -> ObjectId {
self.id
}
pub fn downgrade(&self) -> WeakGcPtr {
WeakGcPtr {
inner: Arc::downgrade(&self.inner),
id: self.id,
}
}
}
impl std::ops::Deref for GcPtr {
type Target = dyn GcObject;
fn deref(&self) -> &Self::Target {
&*self.inner
}
}
impl PartialEq for GcPtr {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for GcPtr {}
impl std::hash::Hash for GcPtr {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
impl WeakGcPtr {
pub fn upgrade(&self) -> Option<GcPtr> {
self.inner.upgrade().map(|inner| GcPtr {
inner,
id: self.id,
})
}
pub fn id(&self) -> ObjectId {
self.id
}
}
static GLOBAL_GC: once_cell::sync::Lazy<GenerationalGc> =
once_cell::sync::Lazy::new(|| GenerationalGc::new(GcConfig::default()));
pub fn gc_alloc<T: GcObject + 'static>(obj: T) -> GcPtr {
GLOBAL_GC.alloc(obj)
}
pub fn gc_add_root(ptr: &GcPtr) {
GLOBAL_GC.add_root(ptr);
}
pub fn gc_remove_root(ptr: &GcPtr) {
GLOBAL_GC.remove_root(ptr);
}
pub fn gc_collect() {
GLOBAL_GC.collect_all();
}
pub fn gc_stats() -> Vec<GcStats> {
GLOBAL_GC.get_stats()
}
pub fn gc_memory_usage() -> Vec<usize> {
GLOBAL_GC.memory_usage()
}
pub fn gc_debug_info() -> GcDebugInfo {
GLOBAL_GC.debug_info()
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
struct MockObject {
generation: AtomicU32,
marked: AtomicBool,
references: Vec<GcPtr>,
size: usize,
}
impl MockObject {
fn new(size: usize) -> Self {
Self {
generation: AtomicU32::new(NURSERY_GENERATION),
marked: AtomicBool::new(false),
references: Vec::new(),
size,
}
}
fn with_references(size: usize, references: Vec<GcPtr>) -> Self {
Self {
generation: AtomicU32::new(NURSERY_GENERATION),
marked: AtomicBool::new(false),
references,
size,
}
}
}
impl GcObject for MockObject {
fn generation(&self) -> GenerationId {
self.generation.load(Ordering::Relaxed)
}
fn set_generation(&mut self, generation: GenerationId) {
self.generation.store(generation, Ordering::Relaxed);
}
fn references(&self) -> Vec<GcPtr> {
self.references.clone()
}
fn mark(&self) {
self.marked.store(true, Ordering::Relaxed);
}
fn is_marked(&self) -> bool {
self.marked.load(Ordering::Relaxed)
}
fn clear_mark(&self) {
self.marked.store(false, Ordering::Relaxed);
}
fn size_hint(&self) -> usize {
self.size
}
}
#[test]
fn test_object_allocation() {
let gc = GenerationalGc::new(GcConfig::default());
let obj = gc.alloc(MockObject::new(100));
assert_eq!(obj.generation(), NURSERY_GENERATION);
assert_eq!(obj.size_hint(), 100);
assert_eq!(gc.object_count(), 1);
}
#[test]
fn test_root_objects() {
let gc = GenerationalGc::new(GcConfig::default());
let obj = gc.alloc(MockObject::new(100));
gc.add_root(&obj);
gc.collect_generation(NURSERY_GENERATION);
assert_eq!(gc.object_count(), 1);
}
#[test]
fn test_object_collection() {
let gc = GenerationalGc::new(GcConfig::default());
let _obj = gc.alloc(MockObject::new(100));
gc.collect_generation(NURSERY_GENERATION);
assert_eq!(gc.object_count(), 0);
}
#[test]
fn test_weak_references() {
let gc = GenerationalGc::new(GcConfig::default());
let obj = gc.alloc(MockObject::new(100));
let weak_ref = gc.downgrade(&obj);
assert!(weak_ref.upgrade().is_some());
drop(obj);
gc.collect_generation(NURSERY_GENERATION);
assert!(weak_ref.upgrade().is_none());
}
#[test]
fn test_gc_statistics() {
let gc = GenerationalGc::new(GcConfig::default());
for i in 0..10 {
gc.alloc(MockObject::new(i * 10));
}
gc.collect_generation(NURSERY_GENERATION);
let stats = gc.get_stats();
assert!(!stats.is_empty());
let last_stats = &stats[stats.len() - 1];
assert_eq!(last_stats.generation, NURSERY_GENERATION);
assert!(last_stats.objects_before > 0);
assert!(last_stats.collection_time_us > 0);
}
}