//! Rate Limiting Performance Benchmark //! //! Measures rate limiter performance: //! - TARGET: <50ns per check //! - Atomic counter operations //! - Token bucket algorithm //! - Sliding window counters //! - Concurrent access patterns use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; use std::collections::HashMap; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; /// Atomic counter-based rate limiter (fastest) struct AtomicRateLimiter { counters: HashMap>, limit: u64, } impl AtomicRateLimiter { fn new(limit: u64) -> Self { Self { counters: HashMap::new(), limit, } } fn check(&self, user_id: &str) -> bool { if let Some(counter) = self.counters.get(user_id) { let count = counter.fetch_add(1, Ordering::Relaxed); count < self.limit } else { true // First request always allowed } } fn with_user(mut self, user_id: &str) -> Self { self.counters .insert(user_id.to_string(), Arc::new(AtomicU64::new(0))); self } } /// Token bucket rate limiter struct TokenBucket { tokens: Mutex, last_refill: Mutex, capacity: f64, refill_rate: f64, // tokens per second } impl TokenBucket { fn new(capacity: f64, refill_rate: f64) -> Self { Self { tokens: Mutex::new(capacity), last_refill: Mutex::new(Instant::now()), capacity, refill_rate, } } fn check(&self) -> bool { let mut tokens = self.tokens.lock().expect("INVARIANT: Lock should not be poisoned"); let mut last_refill = self.last_refill.lock().expect("INVARIANT: Lock should not be poisoned"); let now = Instant::now(); let elapsed = now.duration_since(*last_refill).as_secs_f64(); // Refill tokens *tokens = (*tokens + (elapsed * self.refill_rate)).min(self.capacity); *last_refill = now; if *tokens >= 1.0 { *tokens -= 1.0; true } else { false } } } /// Sliding window counter struct SlidingWindow { window: Mutex>, window_size: Duration, limit: usize, } impl SlidingWindow { fn new(window_size: Duration, limit: usize) -> Self { Self { window: Mutex::new(Vec::new()), window_size, limit, } } fn check(&self) -> bool { let mut window = self.window.lock().expect("INVARIANT: Lock should not be poisoned"); let now = Instant::now(); // Remove expired entries window.retain(|&time| now.duration_since(time) < self.window_size); if window.len() < self.limit { window.push(now); true } else { false } } } /// Benchmark 1: Atomic counter (TARGET: <50ns) fn bench_atomic_rate_limiter(c: &mut Criterion) { let limiter = AtomicRateLimiter::new(1_000_000).with_user("user123"); c.bench_function("atomic_rate_limiter", |b| { b.iter(|| { let allowed = limiter.check(black_box("user123")); black_box(allowed); }); }); } /// Benchmark 2: Token bucket algorithm fn bench_token_bucket(c: &mut Criterion) { let bucket = TokenBucket::new(100.0, 100.0); c.bench_function("token_bucket_rate_limiter", |b| { b.iter(|| { let allowed = bucket.check(); black_box(allowed); }); }); } /// Benchmark 3: Sliding window counter fn bench_sliding_window(c: &mut Criterion) { let window = SlidingWindow::new(Duration::from_secs(1), 100); c.bench_function("sliding_window_rate_limiter", |b| { b.iter(|| { let allowed = window.check(); black_box(allowed); }); }); } /// Benchmark 4: Different user counts fn bench_user_scaling(c: &mut Criterion) { let mut group = c.benchmark_group("rate_limiter_user_scaling"); for num_users in &[10, 100, 1_000, 10_000] { let mut limiter = AtomicRateLimiter::new(1_000_000); for i in 0..*num_users { limiter = limiter.with_user(&format!("user{}", i)); } group.bench_with_input( BenchmarkId::new("atomic_limiter", num_users), num_users, |b, &n| { b.iter(|| { let user_id = format!("user{}", black_box(n / 2)); let allowed = limiter.check(&user_id); black_box(allowed); }); }, ); } group.finish(); } /// Benchmark 5: Burst handling fn bench_burst_handling(c: &mut Criterion) { let bucket = TokenBucket::new(100.0, 100.0); c.bench_function("burst_100_requests", |b| { b.iter(|| { let mut allowed_count = 0; for _ in 0..100 { if bucket.check() { allowed_count += 1; } } black_box(allowed_count); }); }); } /// Benchmark 6: Rate limiter with refill overhead fn bench_refill_overhead(c: &mut Criterion) { let bucket = TokenBucket::new(1000.0, 1000.0); let mut group = c.benchmark_group("refill_overhead"); // Simulate different request patterns group.bench_function("high_frequency_no_wait", |b| { b.iter(|| { let allowed = bucket.check(); black_box(allowed); }); }); group.bench_function("with_small_delay", |b| { b.iter(|| { std::thread::sleep(Duration::from_micros(1)); let allowed = bucket.check(); black_box(allowed); }); }); group.finish(); } /// Benchmark 7: Concurrent access to rate limiter fn bench_concurrent_access(c: &mut Criterion) { use std::thread; let limiter = Arc::new(AtomicRateLimiter::new(1_000_000).with_user("user123")); c.bench_function("concurrent_rate_limiter_4_threads", |b| { b.iter(|| { let mut handles = vec![]; for _ in 0..4 { let limiter_clone = limiter.clone(); let handle = thread::spawn(move || { for _ in 0..1000 { black_box(limiter_clone.check("user123")); } }); handles.push(handle); } for handle in handles { handle.join().expect("INVARIANT: Thread should complete successfully"); } }); }); } /// Benchmark 8: Deny rate (when limit exceeded) fn bench_deny_rate(c: &mut Criterion) { let limiter = AtomicRateLimiter::new(0).with_user("user123"); // Zero limit c.bench_function("rate_limiter_deny_path", |b| { b.iter(|| { let allowed = limiter.check(black_box("user123")); assert!(!allowed); // Should always deny black_box(allowed); }); }); } /// Benchmark 9: Cache hit patterns fn bench_cache_patterns(c: &mut Criterion) { let mut group = c.benchmark_group("cache_hit_patterns"); // Hot path: Same user repeatedly (cache hit) let limiter_hot = AtomicRateLimiter::new(1_000_000).with_user("user123"); group.bench_function("hot_path_same_user", |b| { b.iter(|| { let allowed = limiter_hot.check(black_box("user123")); black_box(allowed); }); }); // Cold path: Different users (cache miss simulation) let mut limiter_cold = AtomicRateLimiter::new(1_000_000); for i in 0..1000 { limiter_cold = limiter_cold.with_user(&format!("user{}", i)); } let mut counter = 0; group.bench_function("cold_path_different_users", |b| { b.iter(|| { counter += 1; let user_id = format!("user{}", counter % 1000); let allowed = limiter_cold.check(&user_id); black_box(allowed); }); }); group.finish(); } /// Benchmark 10: HFT scenario (100K requests/sec target) fn bench_hft_scenario(c: &mut Criterion) { let limiter = AtomicRateLimiter::new(100_000).with_user("hft_trader"); c.bench_function("hft_100k_rps_scenario", |b| { b.iter_custom(|iters| { let start = Instant::now(); for _ in 0..iters { black_box(limiter.check("hft_trader")); } start.elapsed() }); }); } criterion_group!( rate_limiting_benches, bench_atomic_rate_limiter, bench_token_bucket, bench_sliding_window, bench_user_scaling, bench_burst_handling, bench_refill_overhead, bench_concurrent_access, bench_deny_rate, bench_cache_patterns, bench_hft_scenario ); criterion_main!(rate_limiting_benches);