//! VectorClock performance benchmarks //! //! These benchmarks measure the performance improvements from the two-pass → //! single-pass optimization in the happened_before() method. use criterion::{ BenchmarkId, Criterion, black_box, criterion_group, criterion_main, }; use lib::networking::VectorClock; /// Helper to create a vector clock with N nodes fn create_clock_with_nodes(num_nodes: usize) -> VectorClock { let mut clock = VectorClock::new(); for i in 0..num_nodes { let node_id = uuid::Uuid::from_u128(i as u128); clock.increment(node_id); } clock } /// Benchmark: happened_before comparison with small clocks (5 nodes) /// /// This is the common case in typical distributed systems with a small number /// of replicas. fn bench_happened_before_small_clocks(c: &mut Criterion) { let mut group = c.benchmark_group("VectorClock::happened_before (5 nodes)"); group.bench_function("happened_before (true)", |b| { let node = uuid::Uuid::from_u128(1); let mut clock1 = VectorClock::new(); clock1.increment(node); let mut clock2 = VectorClock::new(); clock2.increment(node); clock2.increment(node); b.iter(|| black_box(clock1.happened_before(&clock2))); }); group.bench_function("happened_before (false)", |b| { let node1 = uuid::Uuid::from_u128(1); let node2 = uuid::Uuid::from_u128(2); let mut clock1 = VectorClock::new(); clock1.increment(node1); let mut clock2 = VectorClock::new(); clock2.increment(node2); b.iter(|| black_box(clock1.happened_before(&clock2))); }); group.bench_function("happened_before (concurrent)", |b| { let node1 = uuid::Uuid::from_u128(1); let node2 = uuid::Uuid::from_u128(2); let node3 = uuid::Uuid::from_u128(3); let mut clock1 = create_clock_with_nodes(5); clock1.increment(node1); let mut clock2 = create_clock_with_nodes(5); clock2.increment(node2); clock2.increment(node3); b.iter(|| black_box(clock1.happened_before(&clock2))); }); group.finish(); } /// Benchmark: happened_before comparison with large clocks (100 nodes) /// /// This tests the optimization's effectiveness with larger clock sizes. /// The single-pass algorithm should show better improvements here. fn bench_happened_before_large_clocks(c: &mut Criterion) { let mut group = c.benchmark_group("VectorClock::happened_before (100 nodes)"); group.bench_function("happened_before (100 nodes, true)", |b| { let clock1 = create_clock_with_nodes(100); let mut clock2 = clock1.clone(); let node = uuid::Uuid::from_u128(50); clock2.increment(node); b.iter(|| black_box(clock1.happened_before(&clock2))); }); group.bench_function("happened_before (100 nodes, concurrent)", |b| { let mut clock1 = create_clock_with_nodes(100); let node1 = uuid::Uuid::from_u128(200); clock1.increment(node1); let mut clock2 = create_clock_with_nodes(100); let node2 = uuid::Uuid::from_u128(201); clock2.increment(node2); b.iter(|| black_box(clock1.happened_before(&clock2))); }); group.finish(); } /// Benchmark: happened_before with disjoint node sets /// /// This is the scenario where early exit optimization provides the most /// benefit. fn bench_happened_before_disjoint(c: &mut Criterion) { let mut group = c.benchmark_group("VectorClock::happened_before (disjoint)"); for num_nodes in [5, 20, 50, 100] { group.bench_with_input( BenchmarkId::from_parameter(num_nodes), &num_nodes, |b, &num_nodes| { // Create two clocks with completely different nodes let mut clock1 = VectorClock::new(); for i in 0..num_nodes { clock1.increment(uuid::Uuid::from_u128(i as u128)); } let mut clock2 = VectorClock::new(); for i in 0..num_nodes { clock2.increment(uuid::Uuid::from_u128((i + 1000) as u128)); } b.iter(|| black_box(clock1.happened_before(&clock2))); }, ); } group.finish(); } /// Benchmark: merge operation fn bench_merge(c: &mut Criterion) { let mut group = c.benchmark_group("VectorClock::merge"); for num_nodes in [5, 20, 50, 100] { group.bench_with_input( BenchmarkId::from_parameter(num_nodes), &num_nodes, |b, &num_nodes| { b.iter_batched( || { // Setup: Create two clocks with overlapping nodes let clock1 = create_clock_with_nodes(num_nodes); let clock2 = create_clock_with_nodes(num_nodes / 2); (clock1, clock2) }, |(mut clock1, clock2)| { clock1.merge(&clock2); black_box(clock1) }, criterion::BatchSize::SmallInput, ); }, ); } group.finish(); } /// Benchmark: increment operation fn bench_increment(c: &mut Criterion) { let mut group = c.benchmark_group("VectorClock::increment"); group.bench_function("increment (new node)", |b| { b.iter_batched( || { let clock = VectorClock::new(); let node_id = uuid::Uuid::new_v4(); (clock, node_id) }, |(mut clock, node_id)| { black_box(clock.increment(node_id)); clock }, criterion::BatchSize::SmallInput, ); }); group.bench_function("increment (existing node)", |b| { b.iter_batched( || { let node_id = uuid::Uuid::new_v4(); let mut clock = VectorClock::new(); clock.increment(node_id); (clock, node_id) }, |(mut clock, node_id)| { black_box(clock.increment(node_id)); clock }, criterion::BatchSize::SmallInput, ); }); group.finish(); } /// Benchmark: is_concurrent_with operation fn bench_is_concurrent(c: &mut Criterion) { let mut group = c.benchmark_group("VectorClock::is_concurrent_with"); group.bench_function("concurrent (5 nodes)", |b| { let node1 = uuid::Uuid::from_u128(1); let node2 = uuid::Uuid::from_u128(2); let mut clock1 = VectorClock::new(); clock1.increment(node1); let mut clock2 = VectorClock::new(); clock2.increment(node2); b.iter(|| black_box(clock1.is_concurrent_with(&clock2))); }); group.bench_function("not concurrent (ordered)", |b| { let node = uuid::Uuid::from_u128(1); let mut clock1 = VectorClock::new(); clock1.increment(node); let mut clock2 = VectorClock::new(); clock2.increment(node); clock2.increment(node); b.iter(|| black_box(clock1.is_concurrent_with(&clock2))); }); group.finish(); } /// Benchmark: Realistic workload simulation /// /// Simulates a typical distributed system workflow with increments, merges, /// and comparisons. fn bench_realistic_workload(c: &mut Criterion) { let mut group = c.benchmark_group("VectorClock::realistic_workload"); group.bench_function("3 nodes, 100 operations", |b| { let node1 = uuid::Uuid::from_u128(1); let node2 = uuid::Uuid::from_u128(2); let node3 = uuid::Uuid::from_u128(3); b.iter(|| { let mut clock1 = VectorClock::new(); let mut clock2 = VectorClock::new(); let mut clock3 = VectorClock::new(); // Simulate 100 operations across 3 nodes for i in 0..100 { match i % 7 { | 0 => { clock1.increment(node1); }, | 1 => { clock2.increment(node2); }, | 2 => { clock3.increment(node3); }, | 3 => { clock1.merge(&clock2); }, | 4 => { clock2.merge(&clock3); }, | 5 => { let _ = clock1.happened_before(&clock2); }, | _ => { let _ = clock2.is_concurrent_with(&clock3); }, } } black_box((clock1, clock2, clock3)) }); }); group.finish(); } criterion_group!( benches, bench_happened_before_small_clocks, bench_happened_before_large_clocks, bench_happened_before_disjoint, bench_merge, bench_increment, bench_is_concurrent, bench_realistic_workload, ); criterion_main!(benches);