use crate::arc::Arc;
use crate::semiring::Semiring;
use std::collections::VecDeque;
use std::sync::{Arc as SyncArc, Mutex};
#[derive(Debug)]
pub struct ArcPool<W: Semiring> {
pool: Mutex<VecDeque<Arc<W>>>,
max_pool_size: usize,
stats: Mutex<PoolStats>,
}
#[derive(Debug, Clone, Default)]
pub struct PoolStats {
pub requests: usize,
pub hits: usize,
pub misses: usize,
pub pool_size: usize,
pub max_pool_size_reached: usize,
}
impl<W: Semiring> ArcPool<W> {
pub fn new() -> Self {
Self::with_capacity(1000)
}
pub fn with_capacity(max_size: usize) -> Self {
Self {
pool: Mutex::new(VecDeque::with_capacity(max_size.min(100))),
max_pool_size: max_size,
stats: Mutex::new(PoolStats::default()),
}
}
pub fn get_arc(&self, ilabel: u32, olabel: u32, weight: W, nextstate: u32) -> Arc<W> {
let mut stats = self.stats.lock().unwrap();
stats.requests += 1;
let mut pool = self.pool.lock().unwrap();
stats.pool_size = pool.len();
if let Some(mut arc) = pool.pop_front() {
arc.ilabel = ilabel;
arc.olabel = olabel;
arc.weight = weight;
arc.nextstate = nextstate;
stats.hits += 1;
arc
} else {
stats.misses += 1;
Arc::new(ilabel, olabel, weight, nextstate)
}
}
pub fn return_arc(&self, arc: Arc<W>) {
let mut pool = self.pool.lock().unwrap();
if pool.len() < self.max_pool_size {
pool.push_back(arc);
let mut stats = self.stats.lock().unwrap();
stats.pool_size = pool.len();
stats.max_pool_size_reached = stats.max_pool_size_reached.max(pool.len());
}
}
pub fn stats(&self) -> PoolStats {
self.stats.lock().unwrap().clone()
}
pub fn clear(&self) {
let mut pool = self.pool.lock().unwrap();
pool.clear();
let mut stats = self.stats.lock().unwrap();
stats.pool_size = 0;
}
pub fn preallocate(&self, count: usize) {
let mut pool = self.pool.lock().unwrap();
let to_allocate = count.min(self.max_pool_size - pool.len());
for _ in 0..to_allocate {
pool.push_back(Arc::new(0, 0, W::zero(), 0));
}
let mut stats = self.stats.lock().unwrap();
stats.pool_size = pool.len();
stats.max_pool_size_reached = stats.max_pool_size_reached.max(pool.len());
}
}
impl<W: Semiring> Default for ArcPool<W> {
fn default() -> Self {
Self::new()
}
}
impl PoolStats {
pub fn hit_rate(&self) -> f64 {
if self.requests == 0 {
0.0
} else {
self.hits as f64 / self.requests as f64
}
}
pub fn miss_rate(&self) -> f64 {
1.0 - self.hit_rate()
}
pub fn is_performing_well(&self) -> bool {
self.hit_rate() > 0.8 && self.requests > 10
}
}
pub type SharedArcPool<W> = SyncArc<ArcPool<W>>;
#[derive(Debug)]
pub struct BatchArcAllocator<W: Semiring> {
batches: Mutex<Vec<Vec<Arc<W>>>>,
batch_size: usize,
max_batches: usize,
}
impl<W: Semiring> BatchArcAllocator<W> {
pub fn new() -> Self {
Self::with_config(1000, 10)
}
pub fn with_config(batch_size: usize, max_batches: usize) -> Self {
Self {
batches: Mutex::new(Vec::new()),
batch_size,
max_batches,
}
}
pub fn allocate_batch(&self, count: Option<usize>) -> Vec<Arc<W>> {
let size = count.unwrap_or(self.batch_size);
let mut batches = self.batches.lock().unwrap();
if let Some(pos) = batches.iter().position(|batch| batch.len() >= size) {
let mut batch = batches.remove(pos);
batch.truncate(size);
batch
} else {
(0..size).map(|_| Arc::new(0, 0, W::zero(), 0)).collect()
}
}
pub fn return_batch(&self, batch: Vec<Arc<W>>) {
let mut batches = self.batches.lock().unwrap();
if batches.len() < self.max_batches {
batches.push(batch);
}
}
pub fn clear(&self) {
let mut batches = self.batches.lock().unwrap();
batches.clear();
}
}
impl<W: Semiring> Default for BatchArcAllocator<W> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::prelude::*;
#[test]
fn test_arc_pool_basic() {
let pool = ArcPool::<TropicalWeight>::new();
let arc = pool.get_arc(1, 2, TropicalWeight::new(0.5), 3);
assert_eq!(arc.ilabel, 1);
assert_eq!(arc.olabel, 2);
assert_eq!(arc.nextstate, 3);
pool.return_arc(arc);
let arc2 = pool.get_arc(4, 5, TropicalWeight::new(1.0), 6);
assert_eq!(arc2.ilabel, 4);
assert_eq!(arc2.olabel, 5);
assert_eq!(arc2.nextstate, 6);
}
#[test]
fn test_pool_stats() {
let pool = ArcPool::<TropicalWeight>::new();
let stats = pool.stats();
assert_eq!(stats.requests, 0);
assert_eq!(stats.hits, 0);
assert_eq!(stats.misses, 0);
let arc = pool.get_arc(1, 1, TropicalWeight::new(0.5), 2);
let stats = pool.stats();
assert_eq!(stats.requests, 1);
assert_eq!(stats.hits, 0);
assert_eq!(stats.misses, 1);
pool.return_arc(arc);
let _arc2 = pool.get_arc(2, 2, TropicalWeight::new(1.0), 3);
let stats = pool.stats();
assert_eq!(stats.requests, 2);
assert_eq!(stats.hits, 1);
assert_eq!(stats.misses, 1);
assert_eq!(stats.hit_rate(), 0.5);
}
#[test]
fn test_batch_allocator() {
let allocator = BatchArcAllocator::<TropicalWeight>::new();
let batch = allocator.allocate_batch(Some(100));
assert_eq!(batch.len(), 100);
allocator.return_batch(batch);
let batch2 = allocator.allocate_batch(Some(50));
assert_eq!(batch2.len(), 50);
}
#[test]
fn test_preallocate() {
let pool = ArcPool::<TropicalWeight>::new();
pool.preallocate(10);
let stats = pool.stats();
assert_eq!(stats.pool_size, 10);
let _arc = pool.get_arc(1, 1, TropicalWeight::new(0.5), 2);
let stats = pool.stats();
assert_eq!(stats.hits, 1);
assert_eq!(stats.misses, 0);
}
}