use std::sync::Arc;
use std::time::Instant;
pub struct BenchmarkStrategy {
pub name: String,
pub compress_fn: Arc<dyn Fn(&[String]) -> Vec<String> + Send + Sync>,
}
impl BenchmarkStrategy {
pub fn new(
name: impl Into<String>,
f: impl Fn(&[String]) -> Vec<String> + Send + Sync + 'static,
) -> Self {
Self {
name: name.into(),
compress_fn: Arc::new(f),
}
}
}
#[derive(Debug, Clone)]
pub struct BenchmarkResult {
pub strategy_name: String,
pub ratio: f64,
pub quality_score: f64,
pub elapsed_us: u64,
}
impl BenchmarkResult {
pub fn composite_score(&self) -> f64 {
let speed_factor = 1.0 / (1.0 + self.elapsed_us as f64 / 1000.0);
0.5 * (1.0 - self.ratio) + 0.3 * self.quality_score + 0.2 * speed_factor
}
}
fn quality_score(original: &[String], compressed: &[String]) -> f64 {
if original.is_empty() {
return 1.0;
}
if compressed.is_empty() {
return 0.0;
}
let compressed_set: std::collections::HashSet<&String> = compressed.iter().collect();
let preserved = original.iter().filter(|t| compressed_set.contains(t)).count();
let coverage = preserved as f64 / original.len() as f64;
let ratio = compressed.len() as f64 / original.len() as f64;
let length_penalty = if ratio < 0.2 { ratio / 0.2 } else { 1.0 };
(coverage * length_penalty).clamp(0.0, 1.0)
}
pub fn strategy_every_other(tokens: &[String]) -> Vec<String> {
tokens.iter().step_by(2).cloned().collect()
}
pub fn strategy_importance_top_half(tokens: &[String]) -> Vec<String> {
let keep = (tokens.len() + 1) / 2;
tokens[..keep].to_vec()
}
pub fn strategy_fixed_budget_50pct(tokens: &[String]) -> Vec<String> {
if tokens.is_empty() {
return vec![];
}
let budget = (tokens.len() / 2).max(1);
let step = tokens.len() as f64 / budget as f64;
(0..budget)
.map(|i| {
let idx = (i as f64 * step) as usize;
tokens[idx.min(tokens.len() - 1)].clone()
})
.collect()
}
pub struct CompressionBenchmark {
strategies: Vec<BenchmarkStrategy>,
}
impl CompressionBenchmark {
pub fn new() -> Self {
Self { strategies: Vec::new() }
}
pub fn with_builtins() -> Self {
let mut b = Self::new();
b.add_strategy(BenchmarkStrategy::new("every_other", strategy_every_other));
b.add_strategy(BenchmarkStrategy::new(
"importance_top_half",
strategy_importance_top_half,
));
b.add_strategy(BenchmarkStrategy::new(
"fixed_budget_50pct",
strategy_fixed_budget_50pct,
));
b
}
pub fn add_strategy(&mut self, strategy: BenchmarkStrategy) {
self.strategies.push(strategy);
}
pub fn run(&self, tokens: &[String]) -> Vec<BenchmarkResult> {
self.strategies
.iter()
.map(|s| {
let t0 = Instant::now();
let compressed = (s.compress_fn)(tokens);
let elapsed_us = t0.elapsed().as_micros() as u64;
let ratio = if tokens.is_empty() {
1.0
} else {
compressed.len() as f64 / tokens.len() as f64
};
let qs = quality_score(tokens, &compressed);
BenchmarkResult {
strategy_name: s.name.clone(),
ratio,
quality_score: qs,
elapsed_us,
}
})
.collect()
}
}
impl Default for CompressionBenchmark {
fn default() -> Self {
Self::new()
}
}
pub struct BenchmarkReport;
impl BenchmarkReport {
pub fn render_table(results: &[BenchmarkResult]) -> String {
let mut sorted = results.to_vec();
sorted.sort_by(|a, b| {
b.composite_score()
.partial_cmp(&a.composite_score())
.unwrap_or(std::cmp::Ordering::Equal)
});
let name_w = sorted
.iter()
.map(|r| r.strategy_name.len())
.max()
.unwrap_or(8)
.max(8);
let header = format!(
"{:<name_w$} | {:>7} | {:>7} | {:>10} | {:>7}",
"Strategy", "Ratio", "Quality", "Speed(µs)", "Score",
name_w = name_w,
);
let sep = "-".repeat(header.len());
let mut lines = vec![sep.clone(), header.clone(), sep.clone()];
for r in &sorted {
lines.push(format!(
"{:<name_w$} | {:>7.4} | {:>7.4} | {:>10} | {:>7.4}",
r.strategy_name,
r.ratio,
r.quality_score,
r.elapsed_us,
r.composite_score(),
name_w = name_w,
));
}
lines.push(sep);
lines.join("\n")
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_tokens(n: usize) -> Vec<String> {
(0..n).map(|i| format!("token{}", i)).collect()
}
#[test]
fn test_every_other_half() {
let t = sample_tokens(10);
let r = strategy_every_other(&t);
assert_eq!(r.len(), 5);
assert_eq!(r[0], "token0");
assert_eq!(r[1], "token2");
}
#[test]
fn test_every_other_empty() {
assert!(strategy_every_other(&[]).is_empty());
}
#[test]
fn test_importance_top_half_even() {
let t = sample_tokens(10);
let r = strategy_importance_top_half(&t);
assert_eq!(r.len(), 5);
assert_eq!(r[0], "token0");
}
#[test]
fn test_importance_top_half_odd() {
let t = sample_tokens(7);
let r = strategy_importance_top_half(&t);
assert_eq!(r.len(), 4);
}
#[test]
fn test_fixed_budget_50pct_length() {
let t = sample_tokens(20);
let r = strategy_fixed_budget_50pct(&t);
assert_eq!(r.len(), 10);
}
#[test]
fn test_fixed_budget_empty() {
assert!(strategy_fixed_budget_50pct(&[]).is_empty());
}
#[test]
fn test_fixed_budget_single() {
let t = vec!["only".to_string()];
let r = strategy_fixed_budget_50pct(&t);
assert_eq!(r.len(), 1);
}
#[test]
fn test_benchmark_run_returns_one_per_strategy() {
let b = CompressionBenchmark::with_builtins();
let tokens = sample_tokens(20);
let results = b.run(&tokens);
assert_eq!(results.len(), 3);
}
#[test]
fn test_benchmark_ratio_in_range() {
let b = CompressionBenchmark::with_builtins();
let tokens = sample_tokens(100);
for r in b.run(&tokens) {
assert!(r.ratio >= 0.0 && r.ratio <= 1.0, "ratio={}", r.ratio);
}
}
#[test]
fn test_benchmark_quality_in_range() {
let b = CompressionBenchmark::with_builtins();
let tokens = sample_tokens(50);
for r in b.run(&tokens) {
assert!(r.quality_score >= 0.0 && r.quality_score <= 1.0);
}
}
#[test]
fn test_composite_score_range() {
let r = BenchmarkResult {
strategy_name: "test".to_string(),
ratio: 0.5,
quality_score: 0.8,
elapsed_us: 100,
};
let s = r.composite_score();
assert!(s >= 0.0 && s <= 1.0, "composite={}", s);
}
#[test]
fn test_add_custom_strategy() {
let mut b = CompressionBenchmark::new();
b.add_strategy(BenchmarkStrategy::new("keep_all", |t: &[String]| t.to_vec()));
let tokens = sample_tokens(10);
let results = b.run(&tokens);
assert_eq!(results.len(), 1);
assert!((results[0].ratio - 1.0).abs() < 1e-9);
}
#[test]
fn test_render_table_contains_headers() {
let b = CompressionBenchmark::with_builtins();
let tokens = sample_tokens(20);
let results = b.run(&tokens);
let table = BenchmarkReport::render_table(&results);
assert!(table.contains("Strategy"));
assert!(table.contains("Ratio"));
assert!(table.contains("Quality"));
assert!(table.contains("Speed"));
assert!(table.contains("Score"));
}
#[test]
fn test_render_table_contains_strategy_names() {
let b = CompressionBenchmark::with_builtins();
let tokens = sample_tokens(20);
let results = b.run(&tokens);
let table = BenchmarkReport::render_table(&results);
assert!(table.contains("every_other"));
assert!(table.contains("importance_top_half"));
assert!(table.contains("fixed_budget_50pct"));
}
#[test]
fn test_render_table_sorted() {
let results = vec![
BenchmarkResult {
strategy_name: "slow".to_string(),
ratio: 0.9,
quality_score: 0.3,
elapsed_us: 50000,
},
BenchmarkResult {
strategy_name: "fast_good".to_string(),
ratio: 0.5,
quality_score: 0.9,
elapsed_us: 10,
},
];
let table = BenchmarkReport::render_table(&results);
let fast_pos = table.find("fast_good").unwrap();
let slow_pos = table.find("slow").unwrap();
assert!(fast_pos < slow_pos, "fast_good should rank higher");
}
#[test]
fn test_empty_input() {
let b = CompressionBenchmark::with_builtins();
let results = b.run(&[]);
for r in &results {
assert_eq!(r.ratio, 1.0);
}
}
#[test]
fn test_elapsed_us_recorded() {
let b = CompressionBenchmark::with_builtins();
let tokens = sample_tokens(100);
for r in b.run(&tokens) {
let _ = r.elapsed_us;
}
}
}