use std::collections::BTreeMap;
use std::time::{Duration, Instant};
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::RwLock;
use std::sync::atomic::AtomicU64;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
use std::collections::HashMap;
use std::time::SystemTime;
std::sync::{Arc, Mutex, RwLock}, atomic::{AtomicUsize, Ordering}};
pub struct GenerationalGC {
young_gen: Arc<RwLock<YoungGeneration>>,
old_gen: Arc<RwLock<OldGeneration>>,
stats: Arc<GCStats>,
config: GCConfig,
gc_thread: Option<thread::JoinHandle<()>>,
stop_flag: Arc<AtomicUsize>,
}
#[derive(Debug)]
struct YoungGeneration {
live_objects: HashMap<usize, ObjectInfo>,
free_space: usize,
total_space: usize,
created_at: Instant,
last_gc: Instant,
}
#[derive(Debug)]
struct OldGeneration {
live_objects: HashMap<usize, ObjectInfo>,
total_space: usize,
compaction_count: AtomicUsize,
created_at: Instant,
}
#[derive(Debug, Clone)]
struct ObjectInfo {
address: usize,
size: usize,
created_at: Instant,
last_accessed: Instant,
age: usize,
promotion_threshold: usize,
}
pub struct GCStats {
pub young_gc_count: AtomicU64,
pub old_gc_count: AtomicU64,
pub total_collected_objects: AtomicU64,
pub total_collected_bytes: AtomicU64,
pub total_pause_time_ns: AtomicU64,
pub max_pause_time_ns: AtomicU64,
pub promoted_objects: AtomicU64,
pub promotion_failures: AtomicU64,
}
impl Default for GCStats {
fn default() -> Self {
Self {
young_gc_count: AtomicU64::new(0),
old_gc_count: AtomicU64::new(0),
total_collected_objects: AtomicU64::new(0),
total_collected_bytes: AtomicU64::new(0),
total_pause_time_ns: AtomicU64::new(0),
max_pause_time_ns: AtomicU64::new(0),
promoted_objects: AtomicU64::new(0),
promotion_failures: AtomicU64::new(0),
}
}
}
#[derive(Debug, Clone)]
pub struct GCConfig {
pub young_gen_size: usize,
pub young_gen_max_size: usize,
pub old_gen_size: usize,
pub old_gen_max_size: usize,
pub young_gen_threshold: f64,
pub promotion_age: usize,
pub concurrent_gc_threads: usize,
pub gc_interval_ms: u64,
pub compaction_threshold: f64,
}
impl Default for GCConfig {
fn default() -> Self {
Self {
young_gen_size: 1024 * 1024, young_gen_max_size: 16 * 1024 * 1024, old_gen_size: 64 * 1024 * 1024, old_gen_max_size: 1024 * 1024 * 1024, young_gen_threshold: 0.8, promotion_age: 3, concurrent_gc_threads: 4,
gc_interval_ms: 100, compaction_threshold: 0.9, }
}
}
impl GenerationalGC {
pub fn new(config: GCConfig) -> Self {
let stop_flag: _ = Arc::new(Mutex::new(AtomicUsize::new(0)),;
let stats: _ = Arc::new(Mutex::new(GCStats {)),
young_gc_count: AtomicU64::new(0))
old_gc_count: AtomicU64::new(0),
total_collected_objects: AtomicU64::new(0),
total_collected_bytes: AtomicU64::new(0),
total_pause_time_ns: AtomicU64::new(0),
max_pause_time_ns: AtomicU64::new(0),
promoted_objects: AtomicU64::new(0),
promotion_failures: AtomicU64::new(0),
});
let young_gen: _ = Arc::new(Mutex::new(YoungGeneration {)),
live_objects: HashMap::new())
free_space: config.young_gen_size,
total_space: config.young_gen_size,
created_at: Instant::now(),
last_gc: Instant::now(),
}));
let old_gen: _ = Arc::new(Mutex::new(OldGeneration {)),
live_objects: HashMap::new())
total_space: config.old_gen_size,
compaction_count: AtomicUsize::new(0),
created_at: Instant::now(),
}));
let gc_thread: _ = Some(Self::start_gc_thread(
Arc::clone(young_gen),
Arc::clone(old_gen),
Arc::clone(stats),
Arc::clone(stop_flag),
config.clone(),
));
Self {
young_gen,
old_gen,
stats,
config,
gc_thread,
stop_flag,
}
}
pub fn allocate(&self, size: usize) -> Option<usize> {
let address: _ = self.allocate_in_young_gen(size);
if address.is_some() {
self.stats.total_collected_objects.fetch_add(1, Ordering::Relaxed);
}
address
}
fn allocate_in_young_gen(&self, size: usize) -> Option<usize> {
let mut young_gen = self.young_gen.write().unwrap();
if young_gen.free_space >= size {
let address: _ = self.generate_address();
let now: _ = Instant::now();
young_gen.live_objects.insert(address, ObjectInfo {
address,
size,
created_at: now,
last_accessed: now,
age: 0,
promotion_threshold: self.config.promotion_age,
});
young_gen.free_space -= size;
Some(address)
} else {
drop(young_gen);
self.trigger_young_gc();
self.allocate_in_young_gen(size)
}
}
fn generate_address(&self) -> usize {
let timestamp: _ = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos() as usize;
let random: _ = fastrand::usize(..);
timestamp ^ random
}
fn trigger_young_gc(&self) {
let start_time: _ = Instant::now();
self.stats.young_gc_count.fetch_add(1, Ordering::Relaxed);
let mut young_gen = self.young_gen.write().unwrap();
let (live_objects, collected_bytes) = self.mark_and_sweep(&young_gen.live_objects);
self.stats.total_collected_objects.fetch_add(
(young_gen.live_objects.len() - live_objects.len()) as u64,
Ordering::Relaxed
);
self.stats.total_collected_bytes.fetch_add(collected_bytes, Ordering::Relaxed);
let promoted_objects: _ = self.promote_objects(&mut young_gen, &live_objects);
self.stats.promoted_objects.fetch_add(promoted_objects as u64, Ordering::Relaxed);
young_gen.last_gc = Instant::now();
let pause_time: _ = start_time.elapsed();
let pause_ns: _ = pause_time.as_nanos() as u64;
self.stats.total_pause_time_ns.fetch_add(pause_ns, Ordering::Relaxed);
let current_max: _ = self.stats.max_pause_time_ns.load(Ordering::Relaxed);
if pause_ns > current_max {
self.stats.max_pause_time_ns.store(pause_ns, Ordering::Relaxed);
}
}
fn mark_and_sweep(&self, objects: &HashMap<usize, ObjectInfo>) -> (HashMap<usize, ObjectInfo>, u64) {
let mut live_objects = HashMap::new();
let mut collected_bytes = 0u64;
for (addr, obj_info) in objects.iter() {
if self.is_object_reachable(*addr) {
let mut updated_info = obj_info.clone();
updated_info.last_accessed = Instant::now();
updated_info.age += 1;
live_objects.insert(*addr, updated_info);
} else {
collected_bytes += obj_info.size as u64;
}
}
(live_objects, collected_bytes)
}
fn is_object_reachable(&self, _address: usize) -> bool {
fastrand::f32() > 0.3
}
fn promote_objects(&self, young_gen: &mut YoungGeneration, live_objects: &HashMap<usize, ObjectInfo>) -> usize {
let mut promoted_count = 0;
let mut old_gen = self.old_gen.write().unwrap();
for (addr, obj_info) in live_objects.iter() {
if obj_info.age >= obj_info.promotion_threshold {
old_gen.live_objects.insert(*addr, obj_info.clone());
promoted_count += 1;
}
}
young_gen.live_objects.retain(|addr, _| !old_gen.live_objects.contains_key(addr));
promoted_count
}
fn trigger_old_gc(&self) {
let start_time: _ = Instant::now();
self.stats.old_gc_count.fetch_add(1, Ordering::Relaxed);
let mut old_gen = self.old_gen.write().unwrap();
let usage_ratio: _ = old_gen.live_objects.values()
.map(|obj| obj.size)
.sum::<usize>() as f64 / old_gen.total_space as f64;
if usage_ratio > self.config.compaction_threshold {
self.compact_old_generation(&mut old_gen);
old_gen.compaction_count.fetch_add(1, Ordering::Relaxed);
}
let (live_objects, collected_bytes) = self.mark_and_sweep(&old_gen.live_objects);
old_gen.live_objects = live_objects;
self.stats.total_collected_bytes.fetch_add(collected_bytes, Ordering::Relaxed);
let pause_time: _ = start_time.elapsed();
let pause_ns: _ = pause_time.as_nanos() as u64;
self.stats.total_pause_time_ns.fetch_add(pause_ns, Ordering::Relaxed);
}
fn compact_old_generation(&self, old_gen: &mut OldGeneration) {
let objects: Vec<_> = old_gen.live_objects.values().cloned().collect();
old_gen.live_objects.clear();
for obj in objects {
old_gen.live_objects.insert(obj.address, obj);
}
}
fn start_gc_thread(
young_gen: Arc<RwLock<YoungGeneration>>,
old_gen: Arc<RwLock<OldGeneration>>,
stats: Arc<GCStats>,
stop_flag: Arc<AtomicUsize>,
config: GCConfig,
) -> thread::JoinHandle<()> {
thread::spawn(move || {
let mut last_gc = Instant::now();
while stop_flag.load(Ordering::Relaxed) == 0 {
let now: _ = Instant::now();
if now.duration_since(last_gc) > Duration::from_millis(config.gc_interval_ms) {
if let Ok(young_gen_guard) = young_gen.read() {
let usage_ratio: _ = (young_gen_guard.total_space - young_gen_guard.free_space) as f64
/ young_gen_guard.total_space as f64;
if usage_ratio > config.young_gen_threshold {
drop(young_gen_guard);
Self::trigger_gc_internal(
Arc::clone(young_gen),
Arc::clone(old_gen),
Arc::clone(stats),
&config,
);
}
}
last_gc = now;
}
thread::sleep(Duration::from_millis(10));
}
})
}
fn trigger_gc_internal(
young_gen: Arc<RwLock<YoungGeneration>>,
_old_gen: Arc<RwLock<OldGeneration>>,
_stats: Arc<GCStats>,
config: &GCConfig,
) {
if let Ok(young_gen) = young_gen.read() {
let usage_ratio: _ = (young_gen.total_space - young_gen.free_space) as f64
/ young_gen.total_space as f64;
if usage_ratio > config.young_gen_threshold {
drop(young_gen);
}
}
}
pub fn get_stats(&self) -> GCStatsSnapshot {
let young_gen: _ = self.young_gen.read().unwrap();
let old_gen: _ = self.old_gen.read().unwrap();
GCStatsSnapshot {
young_gc_count: self.stats.young_gc_count.load(Ordering::Relaxed),
old_gc_count: self.stats.old_gc_count.load(Ordering::Relaxed),
total_collected_objects: self.stats.total_collected_objects.load(Ordering::Relaxed),
total_collected_bytes: self.stats.total_collected_bytes.load(Ordering::Relaxed),
total_pause_time_ns: self.stats.total_pause_time_ns.load(Ordering::Relaxed),
max_pause_time_ns: self.stats.max_pause_time_ns.load(Ordering::Relaxed),
promoted_objects: self.stats.promoted_objects.load(Ordering::Relaxed),
promotion_failures: self.stats.promotion_failures.load(Ordering::Relaxed),
young_gen_usage_ratio: (young_gen.total_space - young_gen.free_space) as f64
/ young_gen.total_space as f64,
old_gen_usage_ratio: old_gen.live_objects.values()
.map(|obj| obj.size)
.sum::<usize>() as f64 / old_gen.total_space as f64,
avg_pause_time_ms: if self.stats.young_gc_count.load(Ordering::Relaxed) > 0 {
self.stats.total_pause_time_ns.load(Ordering::Relaxed) as f64
/ self.stats.young_gc_count.load(Ordering::Relaxed) as f64
/ 1_000_000.0
} else {
0.0
},
}
}
pub fn trigger_full_gc(&self) {
self.trigger_young_gc();
self.trigger_old_gc();
}
pub fn stop(&mut self) {
self.stop_flag.store(1, Ordering::Relaxed);
if let Some(handle) = self.gc_thread.take() {
handle.join().unwrap();
}
}
}
#[derive(Debug, Clone)]
pub struct GCStatsSnapshot {
pub young_gc_count: u64,
pub old_gc_count: u64,
pub total_collected_objects: u64,
pub total_collected_bytes: u64,
pub total_pause_time_ns: u64,
pub max_pause_time_ns: u64,
pub promoted_objects: u64,
pub promotion_failures: u64,
pub young_gen_usage_ratio: f64,
pub old_gen_usage_ratio: f64,
pub avg_pause_time_ms: f64,
}
impl Drop for GenerationalGC {
fn drop(&mut self) {
self.stop();
}
}