Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'
This commit is contained in:
707
vendor/ruvector/crates/ruQu/benches/latency_bench.rs
vendored
Normal file
707
vendor/ruvector/crates/ruQu/benches/latency_bench.rs
vendored
Normal file
@@ -0,0 +1,707 @@
|
||||
//! Critical path latency benchmarks for ruQu Coherence Gate.
|
||||
//!
|
||||
//! Primary performance target: **sub-4μs gate decision latency (p99)**
|
||||
//!
|
||||
//! Latency Budget (Target: <4μs p99):
|
||||
//! ```text
|
||||
//! Syndrome Arrival → 0 ns
|
||||
//! Ring buffer append → +50 ns
|
||||
//! Graph update → +200 ns (amortized O(n^{o(1)}))
|
||||
//! Worker Tick → +500 ns (local cut eval)
|
||||
//! Report generation → +100 ns
|
||||
//! TileZero Merge → +500 ns (parallel from 255 tiles)
|
||||
//! Global cut → +300 ns
|
||||
//! Three-filter eval → +100 ns
|
||||
//! Token signing → +500 ns (Ed25519)
|
||||
//! Receipt append → +100 ns
|
||||
//! ─────────────────────────────────
|
||||
//! Total → ~2,350 ns
|
||||
//! ```
|
||||
//!
|
||||
//! Run with: `cargo bench -p ruqu --bench latency_bench`
|
||||
|
||||
use criterion::{
|
||||
black_box, criterion_group, criterion_main, measurement::WallTime, BenchmarkGroup, BenchmarkId,
|
||||
Criterion, SamplingMode,
|
||||
};
|
||||
|
||||
use ruqu::filters::{
|
||||
EvidenceAccumulator as FilterEvidenceAccumulator, EvidenceFilter, FilterConfig, FilterPipeline,
|
||||
ShiftFilter, StructuralFilter, SystemState,
|
||||
};
|
||||
use ruqu::tile::{
|
||||
GateDecision, GateThresholds, LocalCutState, PatchGraph, SyndromeDelta, TileReport, TileZero,
|
||||
WorkerTile,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// HELPER FUNCTIONS
|
||||
// ============================================================================
|
||||
|
||||
/// Create a pre-populated worker tile for benchmarking
|
||||
fn create_benchmark_worker_tile(tile_id: u8, num_vertices: u16, num_edges: u16) -> WorkerTile {
|
||||
let mut tile = WorkerTile::new(tile_id);
|
||||
|
||||
// Add vertices and edges to the patch graph
|
||||
for i in 0..num_vertices.min(255) {
|
||||
tile.patch_graph.ensure_vertex(i);
|
||||
}
|
||||
|
||||
// Add edges in a mesh pattern
|
||||
let mut edges_added = 0u16;
|
||||
'outer: for i in 0..num_vertices.saturating_sub(1) {
|
||||
for j in (i + 1)..num_vertices.min(i + 4) {
|
||||
if edges_added >= num_edges {
|
||||
break 'outer;
|
||||
}
|
||||
if tile.patch_graph.add_edge(i, j, 1000).is_some() {
|
||||
edges_added += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tile.patch_graph.recompute_components();
|
||||
tile
|
||||
}
|
||||
|
||||
/// Create a pre-populated filter pipeline for benchmarking
|
||||
fn create_benchmark_filter_pipeline() -> FilterPipeline {
|
||||
let config = FilterConfig::default();
|
||||
let mut pipeline = FilterPipeline::new(config);
|
||||
|
||||
// Add graph structure
|
||||
for i in 0..50u64 {
|
||||
let _ = pipeline.structural_mut().insert_edge(i, i + 1, 1.0);
|
||||
}
|
||||
pipeline.structural_mut().build();
|
||||
|
||||
// Warm up shift filter with observations
|
||||
for region in 0..10 {
|
||||
for _ in 0..50 {
|
||||
pipeline.shift_mut().update(region, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
// Warm up evidence filter
|
||||
for _ in 0..20 {
|
||||
pipeline.evidence_mut().update(1.5);
|
||||
}
|
||||
|
||||
pipeline
|
||||
}
|
||||
|
||||
/// Create benchmark tile reports
|
||||
fn create_benchmark_tile_reports(count: usize) -> Vec<TileReport> {
|
||||
(1..=count)
|
||||
.map(|i| {
|
||||
let mut report = TileReport::new(i as u8);
|
||||
report.local_cut = 10.0 + (i as f64 * 0.1);
|
||||
report.shift_score = 0.1 + (i as f64 * 0.01);
|
||||
report.e_value = 100.0 + (i as f64);
|
||||
report.num_vertices = 100;
|
||||
report.num_edges = 200;
|
||||
report.num_components = 1;
|
||||
report
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// GATE DECISION LATENCY (Critical Path)
|
||||
// ============================================================================
|
||||
|
||||
/// Benchmark the full decision cycle - the critical <4μs path
|
||||
fn bench_gate_decision(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("gate_decision");
|
||||
group.sampling_mode(SamplingMode::Flat);
|
||||
group.sample_size(1000);
|
||||
|
||||
// Full decision cycle: worker tick + tilezero merge
|
||||
group.bench_function("full_cycle", |b| {
|
||||
let mut tile = create_benchmark_worker_tile(1, 64, 128);
|
||||
let thresholds = GateThresholds::default();
|
||||
let mut tilezero = TileZero::new(thresholds);
|
||||
|
||||
b.iter(|| {
|
||||
// 1. Worker tick - process syndrome delta
|
||||
let delta = SyndromeDelta::new(0, 1, 100);
|
||||
let report = tile.tick(&delta);
|
||||
|
||||
// 2. TileZero merge reports (simulating all 255 tiles with the same report)
|
||||
let reports = vec![report; 10]; // Reduced for single-threaded benchmark
|
||||
let decision = tilezero.merge_reports(reports);
|
||||
|
||||
black_box(decision)
|
||||
});
|
||||
});
|
||||
|
||||
// Worker tick only
|
||||
group.bench_function("worker_tick_only", |b| {
|
||||
let mut tile = create_benchmark_worker_tile(1, 64, 128);
|
||||
let delta = SyndromeDelta::new(0, 1, 100);
|
||||
|
||||
b.iter(|| {
|
||||
let report = tile.tick(black_box(&delta));
|
||||
black_box(report)
|
||||
});
|
||||
});
|
||||
|
||||
// TileZero merge only
|
||||
group.bench_function("tilezero_merge_only", |b| {
|
||||
let thresholds = GateThresholds::default();
|
||||
let mut tilezero = TileZero::new(thresholds);
|
||||
let reports = create_benchmark_tile_reports(255);
|
||||
|
||||
b.iter(|| {
|
||||
let decision = tilezero.merge_reports(black_box(reports.clone()));
|
||||
black_box(decision)
|
||||
});
|
||||
});
|
||||
|
||||
// TileZero merge with varying tile counts
|
||||
for tile_count in [10, 50, 100, 255].iter() {
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("tilezero_merge_tiles", tile_count),
|
||||
tile_count,
|
||||
|b, &count| {
|
||||
let thresholds = GateThresholds::default();
|
||||
let mut tilezero = TileZero::new(thresholds);
|
||||
let reports = create_benchmark_tile_reports(count);
|
||||
|
||||
b.iter(|| {
|
||||
let decision = tilezero.merge_reports(black_box(reports.clone()));
|
||||
black_box(decision)
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// INDIVIDUAL FILTER EVALUATION LATENCY
|
||||
// ============================================================================
|
||||
|
||||
/// Benchmark structural (min-cut) filter evaluation
|
||||
fn bench_structural_filter(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("structural_filter");
|
||||
group.sampling_mode(SamplingMode::Flat);
|
||||
group.sample_size(1000);
|
||||
|
||||
// Basic evaluation with small graph
|
||||
group.bench_function("evaluate_small", |b| {
|
||||
let mut filter = StructuralFilter::new(2.0);
|
||||
for i in 0..20u64 {
|
||||
let _ = filter.insert_edge(i, i + 1, 1.0);
|
||||
}
|
||||
filter.build();
|
||||
let state = SystemState::new(20);
|
||||
|
||||
b.iter(|| {
|
||||
let result = filter.evaluate(black_box(&state));
|
||||
black_box(result)
|
||||
});
|
||||
});
|
||||
|
||||
// Evaluation with medium graph
|
||||
group.bench_function("evaluate_medium", |b| {
|
||||
let mut filter = StructuralFilter::new(2.0);
|
||||
for i in 0..100u64 {
|
||||
let _ = filter.insert_edge(i, (i + 1) % 100, 1.0);
|
||||
let _ = filter.insert_edge(i, (i + 50) % 100, 0.5);
|
||||
}
|
||||
filter.build();
|
||||
let state = SystemState::new(100);
|
||||
|
||||
b.iter(|| {
|
||||
let result = filter.evaluate(black_box(&state));
|
||||
black_box(result)
|
||||
});
|
||||
});
|
||||
|
||||
// Edge insertion (hot path during updates)
|
||||
group.bench_function("insert_edge", |b| {
|
||||
b.iter_batched(
|
||||
|| (StructuralFilter::new(2.0), 0u64),
|
||||
|(mut filter, mut edge_id)| {
|
||||
for _ in 0..100 {
|
||||
let u = edge_id % 256;
|
||||
let v = (edge_id + 1) % 256;
|
||||
let _ = filter.insert_edge(u, v, 1.0);
|
||||
edge_id += 2;
|
||||
}
|
||||
black_box(edge_id)
|
||||
},
|
||||
criterion::BatchSize::SmallInput,
|
||||
);
|
||||
});
|
||||
|
||||
// Edge deletion
|
||||
group.bench_function("delete_edge", |b| {
|
||||
b.iter_batched(
|
||||
|| {
|
||||
let mut filter = StructuralFilter::new(2.0);
|
||||
for i in 0..100u64 {
|
||||
let _ = filter.insert_edge(i, i + 1, 1.0);
|
||||
}
|
||||
filter
|
||||
},
|
||||
|mut filter| {
|
||||
let result = filter.delete_edge(50, 51);
|
||||
black_box(result)
|
||||
},
|
||||
criterion::BatchSize::SmallInput,
|
||||
);
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
/// Benchmark shift (drift detection) filter evaluation
|
||||
fn bench_shift_filter(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("shift_filter");
|
||||
group.sampling_mode(SamplingMode::Flat);
|
||||
group.sample_size(1000);
|
||||
|
||||
// Evaluate with warm filter
|
||||
group.bench_function("evaluate_warm", |b| {
|
||||
let mut filter = ShiftFilter::new(0.5, 100);
|
||||
// Warm up with observations
|
||||
for region in 0..64 {
|
||||
for _ in 0..100 {
|
||||
filter.update(region, 0.5 + (region as f64 * 0.001));
|
||||
}
|
||||
}
|
||||
let state = SystemState::new(100);
|
||||
|
||||
b.iter(|| {
|
||||
let result = filter.evaluate(black_box(&state));
|
||||
black_box(result)
|
||||
});
|
||||
});
|
||||
|
||||
// Evaluate with cold filter
|
||||
group.bench_function("evaluate_cold", |b| {
|
||||
let filter = ShiftFilter::new(0.5, 100);
|
||||
let state = SystemState::new(100);
|
||||
|
||||
b.iter(|| {
|
||||
let result = filter.evaluate(black_box(&state));
|
||||
black_box(result)
|
||||
});
|
||||
});
|
||||
|
||||
// Single update operation
|
||||
group.bench_function("update_single", |b| {
|
||||
let mut filter = ShiftFilter::new(0.5, 100);
|
||||
let mut i = 0usize;
|
||||
|
||||
b.iter(|| {
|
||||
filter.update(black_box(i % 64), black_box(0.5));
|
||||
i += 1;
|
||||
});
|
||||
});
|
||||
|
||||
// Batch update (64 regions)
|
||||
group.bench_function("update_batch_64", |b| {
|
||||
let mut filter = ShiftFilter::new(0.5, 100);
|
||||
|
||||
b.iter(|| {
|
||||
for region in 0..64 {
|
||||
filter.update(black_box(region), black_box(0.5));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
/// Benchmark evidence (e-value) filter evaluation
|
||||
fn bench_evidence_filter(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("evidence_filter");
|
||||
group.sampling_mode(SamplingMode::Flat);
|
||||
group.sample_size(1000);
|
||||
|
||||
// Evaluate with accumulated evidence
|
||||
group.bench_function("evaluate_accumulated", |b| {
|
||||
let mut filter = EvidenceFilter::new(20.0, 0.05);
|
||||
for _ in 0..100 {
|
||||
filter.update(1.5);
|
||||
}
|
||||
let state = SystemState::new(100);
|
||||
|
||||
b.iter(|| {
|
||||
let result = filter.evaluate(black_box(&state));
|
||||
black_box(result)
|
||||
});
|
||||
});
|
||||
|
||||
// Single evidence update
|
||||
group.bench_function("update_single", |b| {
|
||||
let mut filter = EvidenceFilter::new(20.0, 0.05);
|
||||
|
||||
b.iter(|| {
|
||||
filter.update(black_box(1.5));
|
||||
});
|
||||
});
|
||||
|
||||
// Evidence accumulator operations
|
||||
group.bench_function("accumulator_observe", |b| {
|
||||
let mut accumulator = FilterEvidenceAccumulator::new();
|
||||
|
||||
b.iter(|| {
|
||||
accumulator.update(black_box(1.5));
|
||||
});
|
||||
});
|
||||
|
||||
group.bench_function("accumulator_e_value", |b| {
|
||||
let mut accumulator = FilterEvidenceAccumulator::new();
|
||||
for _ in 0..100 {
|
||||
accumulator.update(1.5);
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
let e = accumulator.e_value();
|
||||
black_box(e)
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TILE PROCESSING LATENCY
|
||||
// ============================================================================
|
||||
|
||||
/// Benchmark worker tile tick processing
|
||||
fn bench_worker_tile_tick(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("worker_tile_tick");
|
||||
group.sampling_mode(SamplingMode::Flat);
|
||||
group.sample_size(1000);
|
||||
|
||||
// Tick with syndrome delta
|
||||
group.bench_function("tick_syndrome", |b| {
|
||||
let mut tile = create_benchmark_worker_tile(1, 64, 128);
|
||||
let delta = SyndromeDelta::new(0, 1, 100);
|
||||
|
||||
b.iter(|| {
|
||||
let report = tile.tick(black_box(&delta));
|
||||
black_box(report)
|
||||
});
|
||||
});
|
||||
|
||||
// Tick with edge addition
|
||||
group.bench_function("tick_edge_add", |b| {
|
||||
let mut tile = create_benchmark_worker_tile(1, 64, 128);
|
||||
let delta = SyndromeDelta::edge_add(10, 20, 1000);
|
||||
|
||||
b.iter(|| {
|
||||
let report = tile.tick(black_box(&delta));
|
||||
black_box(report)
|
||||
});
|
||||
});
|
||||
|
||||
// Tick with edge removal
|
||||
group.bench_function("tick_edge_remove", |b| {
|
||||
b.iter_batched(
|
||||
|| {
|
||||
let mut tile = create_benchmark_worker_tile(1, 64, 128);
|
||||
// Add edge before removing
|
||||
let _ = tile.patch_graph.add_edge(5, 6, 1000);
|
||||
(tile, SyndromeDelta::edge_remove(5, 6))
|
||||
},
|
||||
|(mut tile, delta)| {
|
||||
let report = tile.tick(&delta);
|
||||
black_box(report)
|
||||
},
|
||||
criterion::BatchSize::SmallInput,
|
||||
);
|
||||
});
|
||||
|
||||
// Varying graph sizes
|
||||
for (vertices, edges) in [(32, 64), (64, 128), (128, 256), (200, 400)].iter() {
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("tick_graph_size", format!("v{}e{}", vertices, edges)),
|
||||
&(*vertices, *edges),
|
||||
|b, &(v, e)| {
|
||||
let mut tile = create_benchmark_worker_tile(1, v, e);
|
||||
let delta = SyndromeDelta::new(0, 1, 100);
|
||||
|
||||
b.iter(|| {
|
||||
let report = tile.tick(black_box(&delta));
|
||||
black_box(report)
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
/// Benchmark TileZero merge operations
|
||||
fn bench_tilezero_merge(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("tilezero_merge");
|
||||
group.sampling_mode(SamplingMode::Flat);
|
||||
group.sample_size(1000);
|
||||
|
||||
// Merge leading to PERMIT
|
||||
group.bench_function("merge_permit", |b| {
|
||||
let thresholds = GateThresholds::default();
|
||||
let mut tilezero = TileZero::new(thresholds);
|
||||
|
||||
let reports: Vec<TileReport> = (1..=100)
|
||||
.map(|i| {
|
||||
let mut report = TileReport::new(i as u8);
|
||||
report.local_cut = 10.0;
|
||||
report.shift_score = 0.1;
|
||||
report.e_value = 200.0;
|
||||
report
|
||||
})
|
||||
.collect();
|
||||
|
||||
b.iter(|| {
|
||||
let decision = tilezero.merge_reports(black_box(reports.clone()));
|
||||
debug_assert_eq!(decision, GateDecision::Permit);
|
||||
black_box(decision)
|
||||
});
|
||||
});
|
||||
|
||||
// Merge leading to DENY (structural)
|
||||
group.bench_function("merge_deny_structural", |b| {
|
||||
let thresholds = GateThresholds::default();
|
||||
let mut tilezero = TileZero::new(thresholds);
|
||||
|
||||
let reports: Vec<TileReport> = (1..=100)
|
||||
.map(|i| {
|
||||
let mut report = TileReport::new(i as u8);
|
||||
report.local_cut = 1.0; // Below threshold
|
||||
report.shift_score = 0.1;
|
||||
report.e_value = 200.0;
|
||||
report
|
||||
})
|
||||
.collect();
|
||||
|
||||
b.iter(|| {
|
||||
let decision = tilezero.merge_reports(black_box(reports.clone()));
|
||||
debug_assert_eq!(decision, GateDecision::Deny);
|
||||
black_box(decision)
|
||||
});
|
||||
});
|
||||
|
||||
// Merge leading to DEFER (shift)
|
||||
group.bench_function("merge_defer_shift", |b| {
|
||||
let thresholds = GateThresholds::default();
|
||||
let mut tilezero = TileZero::new(thresholds);
|
||||
|
||||
let reports: Vec<TileReport> = (1..=100)
|
||||
.map(|i| {
|
||||
let mut report = TileReport::new(i as u8);
|
||||
report.local_cut = 10.0;
|
||||
report.shift_score = 0.8; // Above threshold
|
||||
report.e_value = 200.0;
|
||||
report
|
||||
})
|
||||
.collect();
|
||||
|
||||
b.iter(|| {
|
||||
let decision = tilezero.merge_reports(black_box(reports.clone()));
|
||||
debug_assert_eq!(decision, GateDecision::Defer);
|
||||
black_box(decision)
|
||||
});
|
||||
});
|
||||
|
||||
// Permit token issuance
|
||||
group.bench_function("issue_permit", |b| {
|
||||
let thresholds = GateThresholds::default();
|
||||
let tilezero = TileZero::new(thresholds);
|
||||
let decision = GateDecision::Permit;
|
||||
|
||||
b.iter(|| {
|
||||
let token = tilezero.issue_permit(black_box(&decision));
|
||||
black_box(token)
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// PATCH GRAPH LATENCY
|
||||
// ============================================================================
|
||||
|
||||
/// Benchmark patch graph operations (critical for structural filter)
|
||||
fn bench_patch_graph_operations(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("patch_graph");
|
||||
group.sampling_mode(SamplingMode::Flat);
|
||||
group.sample_size(1000);
|
||||
|
||||
// Edge addition
|
||||
group.bench_function("add_edge", |b| {
|
||||
b.iter_batched(
|
||||
PatchGraph::new,
|
||||
|mut graph| {
|
||||
for edge_count in 0..100u16 {
|
||||
let v1 = (edge_count * 2) % 256;
|
||||
let v2 = (edge_count * 2 + 1) % 256;
|
||||
let _ = graph.add_edge(v1, v2, 1000);
|
||||
}
|
||||
black_box(graph.num_edges)
|
||||
},
|
||||
criterion::BatchSize::SmallInput,
|
||||
);
|
||||
});
|
||||
|
||||
// Edge removal
|
||||
group.bench_function("remove_edge", |b| {
|
||||
b.iter_batched(
|
||||
|| {
|
||||
let mut graph = PatchGraph::new();
|
||||
for i in 0..100u16 {
|
||||
let _ = graph.add_edge(i, i + 1, 1000);
|
||||
}
|
||||
graph
|
||||
},
|
||||
|mut graph| {
|
||||
let removed = graph.remove_edge(50, 51);
|
||||
black_box(removed)
|
||||
},
|
||||
criterion::BatchSize::SmallInput,
|
||||
);
|
||||
});
|
||||
|
||||
// Local cut estimation
|
||||
group.bench_function("estimate_local_cut", |b| {
|
||||
let mut graph = PatchGraph::new();
|
||||
for i in 0..100u16 {
|
||||
let _ = graph.add_edge(i, (i + 1) % 100, 1000);
|
||||
let _ = graph.add_edge(i, (i + 50) % 100, 500);
|
||||
}
|
||||
graph.recompute_components();
|
||||
|
||||
b.iter(|| {
|
||||
let cut = graph.estimate_local_cut();
|
||||
black_box(cut)
|
||||
});
|
||||
});
|
||||
|
||||
// Component recomputation
|
||||
group.bench_function("recompute_components", |b| {
|
||||
let mut graph = PatchGraph::new();
|
||||
for i in 0..100u16 {
|
||||
let _ = graph.add_edge(i, (i + 1) % 100, 1000);
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
graph.status |= PatchGraph::STATUS_DIRTY;
|
||||
let count = graph.recompute_components();
|
||||
black_box(count)
|
||||
});
|
||||
});
|
||||
|
||||
// Boundary candidate identification
|
||||
group.bench_function("identify_boundary_candidates", |b| {
|
||||
let mut graph = PatchGraph::new();
|
||||
for i in 0..100u16 {
|
||||
let _ = graph.add_edge(i, (i + 1) % 100, 1000);
|
||||
}
|
||||
graph.recompute_components();
|
||||
let mut candidates = [0u16; 64];
|
||||
|
||||
b.iter(|| {
|
||||
let count = graph.identify_boundary_candidates(&mut candidates);
|
||||
black_box(count)
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// LOCAL CUT STATE LATENCY
|
||||
// ============================================================================
|
||||
|
||||
/// Benchmark local cut state operations
|
||||
fn bench_local_cut_state(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("local_cut_state");
|
||||
group.sampling_mode(SamplingMode::Flat);
|
||||
group.sample_size(1000);
|
||||
|
||||
// Update from graph
|
||||
group.bench_function("update_from_graph", |b| {
|
||||
let mut graph = PatchGraph::new();
|
||||
for i in 0..100u16 {
|
||||
let _ = graph.add_edge(i, (i + 1) % 100, 1000);
|
||||
}
|
||||
graph.recompute_components();
|
||||
|
||||
let mut cut_state = LocalCutState::new();
|
||||
|
||||
b.iter(|| {
|
||||
cut_state.update_from_graph(&graph);
|
||||
black_box(cut_state.cut_value)
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// FILTER PIPELINE LATENCY
|
||||
// ============================================================================
|
||||
|
||||
/// Benchmark full filter pipeline evaluation
|
||||
fn bench_filter_pipeline(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("filter_pipeline");
|
||||
group.sampling_mode(SamplingMode::Flat);
|
||||
group.sample_size(1000);
|
||||
|
||||
// Full evaluation
|
||||
group.bench_function("evaluate_full", |b| {
|
||||
let pipeline = create_benchmark_filter_pipeline();
|
||||
let state = SystemState::new(100);
|
||||
|
||||
b.iter(|| {
|
||||
let result = pipeline.evaluate(black_box(&state));
|
||||
black_box(result)
|
||||
});
|
||||
});
|
||||
|
||||
// Cold start evaluation
|
||||
group.bench_function("evaluate_cold", |b| {
|
||||
b.iter_batched(
|
||||
|| {
|
||||
let config = FilterConfig::default();
|
||||
let pipeline = FilterPipeline::new(config);
|
||||
let state = SystemState::new(100);
|
||||
(pipeline, state)
|
||||
},
|
||||
|(pipeline, state)| {
|
||||
let result = pipeline.evaluate(&state);
|
||||
black_box(result)
|
||||
},
|
||||
criterion::BatchSize::SmallInput,
|
||||
);
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// CRITERION GROUPS
|
||||
// ============================================================================
|
||||
|
||||
criterion_group!(
|
||||
latency_benches,
|
||||
bench_gate_decision,
|
||||
bench_structural_filter,
|
||||
bench_shift_filter,
|
||||
bench_evidence_filter,
|
||||
bench_worker_tile_tick,
|
||||
bench_tilezero_merge,
|
||||
bench_patch_graph_operations,
|
||||
bench_local_cut_state,
|
||||
bench_filter_pipeline,
|
||||
);
|
||||
|
||||
criterion_main!(latency_benches);
|
||||
619
vendor/ruvector/crates/ruQu/benches/memory_bench.rs
vendored
Normal file
619
vendor/ruvector/crates/ruQu/benches/memory_bench.rs
vendored
Normal file
@@ -0,0 +1,619 @@
|
||||
//! Memory efficiency benchmarks for ruQu Coherence Gate.
|
||||
//!
|
||||
//! Memory Targets:
|
||||
//! - Per-tile memory usage: **<64KB**
|
||||
//! - Allocation counts per cycle: **0 (steady state)**
|
||||
//! - Cache line efficiency: **>80%**
|
||||
//!
|
||||
//! Run with: `cargo bench -p ruqu --bench memory_bench`
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
|
||||
use std::alloc::{GlobalAlloc, Layout, System};
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use ruqu::filters::{FilterConfig, FilterPipeline, ShiftFilter, StructuralFilter};
|
||||
use ruqu::syndrome::{DetectorBitmap, SyndromeBuffer, SyndromeRound};
|
||||
use ruqu::tile::{
|
||||
EvidenceAccumulator, GateThresholds, LocalCutState, PatchGraph, ReceiptLog, SyndromBuffer,
|
||||
SyndromeDelta, TileReport, TileZero, WorkerTile,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// ALLOCATION TRACKING ALLOCATOR
|
||||
// ============================================================================
|
||||
|
||||
/// Global allocation counter for tracking allocations
|
||||
static ALLOC_COUNT: AtomicUsize = AtomicUsize::new(0);
|
||||
static DEALLOC_COUNT: AtomicUsize = AtomicUsize::new(0);
|
||||
static BYTES_ALLOCATED: AtomicUsize = AtomicUsize::new(0);
|
||||
static BYTES_DEALLOCATED: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
/// Reset allocation counters
|
||||
fn reset_allocation_counters() {
|
||||
ALLOC_COUNT.store(0, Ordering::SeqCst);
|
||||
DEALLOC_COUNT.store(0, Ordering::SeqCst);
|
||||
BYTES_ALLOCATED.store(0, Ordering::SeqCst);
|
||||
BYTES_DEALLOCATED.store(0, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
/// Get allocation statistics
|
||||
fn get_allocation_stats() -> (usize, usize, usize, usize) {
|
||||
(
|
||||
ALLOC_COUNT.load(Ordering::SeqCst),
|
||||
DEALLOC_COUNT.load(Ordering::SeqCst),
|
||||
BYTES_ALLOCATED.load(Ordering::SeqCst),
|
||||
BYTES_DEALLOCATED.load(Ordering::SeqCst),
|
||||
)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SIZE VERIFICATION BENCHMARKS
|
||||
// ============================================================================
|
||||
|
||||
/// Benchmark and verify structure sizes
|
||||
fn bench_structure_sizes(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("structure_sizes");
|
||||
|
||||
// Report sizes (this is informational, not a timed benchmark)
|
||||
println!("\n=== Structure Sizes ===");
|
||||
println!(
|
||||
"WorkerTile: {} bytes",
|
||||
std::mem::size_of::<WorkerTile>()
|
||||
);
|
||||
println!(
|
||||
"PatchGraph: {} bytes",
|
||||
std::mem::size_of::<PatchGraph>()
|
||||
);
|
||||
println!(
|
||||
"SyndromBuffer: {} bytes",
|
||||
std::mem::size_of::<SyndromBuffer>()
|
||||
);
|
||||
println!(
|
||||
"EvidenceAccumulator: {} bytes",
|
||||
std::mem::size_of::<EvidenceAccumulator>()
|
||||
);
|
||||
println!(
|
||||
"LocalCutState: {} bytes",
|
||||
std::mem::size_of::<LocalCutState>()
|
||||
);
|
||||
println!(
|
||||
"TileReport: {} bytes",
|
||||
std::mem::size_of::<TileReport>()
|
||||
);
|
||||
println!(
|
||||
"DetectorBitmap: {} bytes",
|
||||
std::mem::size_of::<DetectorBitmap>()
|
||||
);
|
||||
println!(
|
||||
"SyndromeRound: {} bytes",
|
||||
std::mem::size_of::<SyndromeRound>()
|
||||
);
|
||||
println!(
|
||||
"SyndromeDelta: {} bytes",
|
||||
std::mem::size_of::<SyndromeDelta>()
|
||||
);
|
||||
println!();
|
||||
|
||||
// Verify 64KB budget
|
||||
let total_tile_size = std::mem::size_of::<WorkerTile>();
|
||||
let budget = 65536; // 64KB
|
||||
println!(
|
||||
"WorkerTile size: {} bytes ({:.1}% of 64KB budget)",
|
||||
total_tile_size,
|
||||
(total_tile_size as f64 / budget as f64) * 100.0
|
||||
);
|
||||
|
||||
// Benchmark size computation (ensures compiler doesn't optimize away)
|
||||
group.bench_function("size_of_worker_tile", |b| {
|
||||
b.iter(|| black_box(std::mem::size_of::<WorkerTile>()));
|
||||
});
|
||||
|
||||
group.bench_function("size_of_patch_graph", |b| {
|
||||
b.iter(|| black_box(std::mem::size_of::<PatchGraph>()));
|
||||
});
|
||||
|
||||
group.bench_function("size_of_tile_report", |b| {
|
||||
b.iter(|| black_box(std::mem::size_of::<TileReport>()));
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// PER-TILE MEMORY USAGE
|
||||
// ============================================================================
|
||||
|
||||
/// Benchmark per-tile memory usage
|
||||
fn bench_per_tile_memory(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("per_tile_memory");
|
||||
|
||||
// WorkerTile memory footprint
|
||||
let worker_tile_size = std::mem::size_of::<WorkerTile>();
|
||||
assert!(
|
||||
worker_tile_size <= 131072, // 128KB max (some padding allowed)
|
||||
"WorkerTile exceeds memory budget: {} bytes",
|
||||
worker_tile_size
|
||||
);
|
||||
|
||||
// Benchmark WorkerTile creation (measures stack allocation)
|
||||
group.bench_function("create_worker_tile", |b| {
|
||||
b.iter(|| {
|
||||
let tile = WorkerTile::new(1);
|
||||
black_box(&tile);
|
||||
// Note: WorkerTile is large, measure creation overhead
|
||||
});
|
||||
});
|
||||
|
||||
// Benchmark WorkerTile reset (should be allocation-free)
|
||||
group.bench_function("reset_worker_tile", |b| {
|
||||
let mut tile = WorkerTile::new(1);
|
||||
// Populate with some data
|
||||
for i in 0..50u16 {
|
||||
let _ = tile.patch_graph.add_edge(i, i + 1, 1000);
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
tile.reset();
|
||||
black_box(&tile);
|
||||
});
|
||||
});
|
||||
|
||||
// Benchmark PatchGraph memory efficiency
|
||||
group.bench_function("patch_graph_memory", |b| {
|
||||
b.iter(|| {
|
||||
let graph = PatchGraph::new();
|
||||
black_box(&graph);
|
||||
black_box(std::mem::size_of_val(&graph));
|
||||
});
|
||||
});
|
||||
|
||||
// Benchmark SyndromBuffer memory efficiency
|
||||
group.bench_function("syndrom_buffer_memory", |b| {
|
||||
b.iter(|| {
|
||||
let buffer = SyndromBuffer::new();
|
||||
black_box(&buffer);
|
||||
black_box(std::mem::size_of_val(&buffer));
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// ALLOCATION-FREE OPERATIONS
|
||||
// ============================================================================
|
||||
|
||||
/// Benchmark operations that should be allocation-free in steady state
|
||||
fn bench_allocation_free_ops(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("allocation_free");
|
||||
|
||||
// Worker tile tick should be allocation-free
|
||||
group.bench_function("worker_tick_no_alloc", |b| {
|
||||
let mut tile = WorkerTile::new(1);
|
||||
// Pre-populate
|
||||
for i in 0..50u16 {
|
||||
let _ = tile.patch_graph.add_edge(i, i + 1, 1000);
|
||||
}
|
||||
tile.patch_graph.recompute_components();
|
||||
|
||||
let delta = SyndromeDelta::new(0, 1, 100);
|
||||
|
||||
b.iter(|| {
|
||||
let report = tile.tick(&delta);
|
||||
black_box(report);
|
||||
});
|
||||
});
|
||||
|
||||
// PatchGraph operations should be allocation-free
|
||||
group.bench_function("patch_graph_ops_no_alloc", |b| {
|
||||
let mut graph = PatchGraph::new();
|
||||
for i in 0..100u16 {
|
||||
let _ = graph.add_edge(i, (i + 1) % 100, 1000);
|
||||
}
|
||||
graph.recompute_components();
|
||||
|
||||
b.iter(|| {
|
||||
// These operations should not allocate
|
||||
let cut = graph.estimate_local_cut();
|
||||
let mut candidates = [0u16; 64];
|
||||
let count = graph.identify_boundary_candidates(&mut candidates);
|
||||
black_box((cut, count));
|
||||
});
|
||||
});
|
||||
|
||||
// DetectorBitmap operations should be allocation-free
|
||||
group.bench_function("bitmap_ops_no_alloc", |b| {
|
||||
let mut a = DetectorBitmap::new(1024);
|
||||
let mut bb = DetectorBitmap::new(1024);
|
||||
for i in (0..512).step_by(2) {
|
||||
a.set(i, true);
|
||||
}
|
||||
for i in (256..768).step_by(2) {
|
||||
bb.set(i, true);
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
let result = a.xor(&bb);
|
||||
let count = result.popcount();
|
||||
black_box(count);
|
||||
});
|
||||
});
|
||||
|
||||
// TileReport copy should be allocation-free
|
||||
group.bench_function("tile_report_copy_no_alloc", |b| {
|
||||
let mut report = TileReport::new(1);
|
||||
report.local_cut = 10.0;
|
||||
report.shift_score = 0.1;
|
||||
report.e_value = 200.0;
|
||||
|
||||
b.iter(|| {
|
||||
let copy = report;
|
||||
black_box(copy);
|
||||
});
|
||||
});
|
||||
|
||||
// Evidence accumulator operations should be allocation-free
|
||||
group.bench_function("evidence_update_no_alloc", |b| {
|
||||
let mut evidence = EvidenceAccumulator::new();
|
||||
|
||||
b.iter(|| {
|
||||
evidence.observe(1000);
|
||||
let e = evidence.e_value();
|
||||
black_box(e);
|
||||
});
|
||||
});
|
||||
|
||||
// LocalCutState update should be allocation-free
|
||||
group.bench_function("local_cut_update_no_alloc", |b| {
|
||||
let mut graph = PatchGraph::new();
|
||||
for i in 0..100u16 {
|
||||
let _ = graph.add_edge(i, (i + 1) % 100, 1000);
|
||||
}
|
||||
graph.recompute_components();
|
||||
|
||||
let mut cut_state = LocalCutState::new();
|
||||
|
||||
b.iter(|| {
|
||||
cut_state.update_from_graph(&graph);
|
||||
black_box(&cut_state);
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// CACHE LINE EFFICIENCY
|
||||
// ============================================================================
|
||||
|
||||
/// Benchmark cache line efficiency
|
||||
fn bench_cache_efficiency(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("cache_efficiency");
|
||||
|
||||
const CACHE_LINE_SIZE: usize = 64;
|
||||
|
||||
// Verify cache-line alignment
|
||||
println!("\n=== Cache Line Alignment ===");
|
||||
println!(
|
||||
"TileReport alignment: {} bytes (cache line: {})",
|
||||
std::mem::align_of::<TileReport>(),
|
||||
CACHE_LINE_SIZE
|
||||
);
|
||||
println!(
|
||||
"PatchGraph alignment: {} bytes",
|
||||
std::mem::align_of::<PatchGraph>()
|
||||
);
|
||||
println!(
|
||||
"SyndromBuffer alignment: {} bytes",
|
||||
std::mem::align_of::<SyndromBuffer>()
|
||||
);
|
||||
println!(
|
||||
"DetectorBitmap alignment: {} bytes",
|
||||
std::mem::align_of::<DetectorBitmap>()
|
||||
);
|
||||
println!();
|
||||
|
||||
// Sequential access pattern (cache-friendly)
|
||||
group.bench_function("sequential_access", |b| {
|
||||
let mut graph = PatchGraph::new();
|
||||
for i in 0..200u16 {
|
||||
graph.ensure_vertex(i);
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
let mut sum = 0u32;
|
||||
for i in 0..200 {
|
||||
if graph.vertices[i].is_active() {
|
||||
sum += graph.vertices[i].degree as u32;
|
||||
}
|
||||
}
|
||||
black_box(sum);
|
||||
});
|
||||
});
|
||||
|
||||
// Strided access pattern (potential cache misses)
|
||||
group.bench_function("strided_access", |b| {
|
||||
let mut graph = PatchGraph::new();
|
||||
for i in 0..200u16 {
|
||||
graph.ensure_vertex(i);
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
let mut sum = 0u32;
|
||||
// Access every 8th element (stride across multiple cache lines)
|
||||
for i in (0..200).step_by(8) {
|
||||
if graph.vertices[i].is_active() {
|
||||
sum += graph.vertices[i].degree as u32;
|
||||
}
|
||||
}
|
||||
black_box(sum);
|
||||
});
|
||||
});
|
||||
|
||||
// TileReport array access (should be cache-line aligned)
|
||||
group.bench_function("tile_report_array_access", |b| {
|
||||
let reports: Vec<TileReport> = (1..=255)
|
||||
.map(|i| {
|
||||
let mut r = TileReport::new(i);
|
||||
r.local_cut = i as f64;
|
||||
r
|
||||
})
|
||||
.collect();
|
||||
|
||||
b.iter(|| {
|
||||
let mut sum = 0.0f64;
|
||||
for report in &reports {
|
||||
sum += report.local_cut;
|
||||
}
|
||||
black_box(sum);
|
||||
});
|
||||
});
|
||||
|
||||
// DetectorBitmap word access (should be aligned)
|
||||
group.bench_function("bitmap_word_access", |b| {
|
||||
let mut bitmap = DetectorBitmap::new(1024);
|
||||
for i in (0..1024).step_by(3) {
|
||||
bitmap.set(i, true);
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
let raw = bitmap.raw_bits();
|
||||
let mut sum = 0u64;
|
||||
for word in raw {
|
||||
sum = sum.wrapping_add(*word);
|
||||
}
|
||||
black_box(sum);
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// MEMORY POOL SIMULATION
|
||||
// ============================================================================
|
||||
|
||||
/// Benchmark simulated memory pool operations
|
||||
fn bench_memory_pool(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("memory_pool");
|
||||
|
||||
// Pre-allocated tile pool
|
||||
group.bench_function("tile_pool_reuse", |b| {
|
||||
// Simulate a pool of worker tiles
|
||||
let mut tile_pool: Vec<WorkerTile> = (1..=10).map(|i| WorkerTile::new(i)).collect();
|
||||
|
||||
let delta = SyndromeDelta::new(0, 1, 100);
|
||||
|
||||
b.iter(|| {
|
||||
// Use tiles from pool without allocation
|
||||
for tile in &mut tile_pool {
|
||||
let report = tile.tick(&delta);
|
||||
black_box(&report);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Pre-allocated report buffer
|
||||
group.bench_function("report_buffer_reuse", |b| {
|
||||
// Simulate a reusable report buffer
|
||||
let mut report_buffer: [TileReport; 255] = [TileReport::default(); 255];
|
||||
|
||||
b.iter(|| {
|
||||
// Fill buffer without allocation
|
||||
for i in 0..255 {
|
||||
report_buffer[i].tile_id = i as u8;
|
||||
report_buffer[i].local_cut = 10.0;
|
||||
report_buffer[i].shift_score = 0.1;
|
||||
report_buffer[i].e_value = 200.0;
|
||||
}
|
||||
black_box(&report_buffer);
|
||||
});
|
||||
});
|
||||
|
||||
// Pre-allocated syndrome round buffer
|
||||
group.bench_function("syndrome_round_reuse", |b| {
|
||||
let mut buffer = SyndromeBuffer::new(1024);
|
||||
let mut round_id = 0u64;
|
||||
// Pre-fill
|
||||
for i in 0..1024 {
|
||||
let round = SyndromeRound::new(i, i, i * 1000, DetectorBitmap::new(64), 0);
|
||||
buffer.push(round);
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
// Push rounds (reusing buffer space)
|
||||
for _ in 0..100 {
|
||||
let round = SyndromeRound::new(
|
||||
round_id,
|
||||
round_id,
|
||||
round_id * 1000,
|
||||
DetectorBitmap::new(64),
|
||||
0,
|
||||
);
|
||||
buffer.push(round);
|
||||
round_id += 1;
|
||||
}
|
||||
black_box(&buffer);
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// HEAP ALLOCATION BENCHMARKS
|
||||
// ============================================================================
|
||||
|
||||
/// Benchmark operations that require heap allocation
|
||||
fn bench_heap_allocations(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("heap_allocations");
|
||||
|
||||
// Filter pipeline (requires heap for collections)
|
||||
group.bench_function("filter_pipeline_create", |b| {
|
||||
b.iter(|| {
|
||||
let config = FilterConfig::default();
|
||||
let pipeline = FilterPipeline::new(config);
|
||||
black_box(pipeline);
|
||||
});
|
||||
});
|
||||
|
||||
// TileZero creation (requires heap)
|
||||
group.bench_function("tilezero_create", |b| {
|
||||
b.iter(|| {
|
||||
let thresholds = GateThresholds::default();
|
||||
let tilezero = TileZero::new(thresholds);
|
||||
black_box(tilezero);
|
||||
});
|
||||
});
|
||||
|
||||
// ReceiptLog append (heap allocation)
|
||||
group.bench_function("receipt_log_grow", |b| {
|
||||
b.iter_batched(
|
||||
ReceiptLog::new,
|
||||
|mut log| {
|
||||
for i in 0..100 {
|
||||
log.append(ruqu::tile::GateDecision::Permit, i, i * 1000, [0u8; 32]);
|
||||
}
|
||||
black_box(&log);
|
||||
},
|
||||
criterion::BatchSize::SmallInput,
|
||||
);
|
||||
});
|
||||
|
||||
// SyndromeBuffer create (heap allocation)
|
||||
group.bench_function("syndrome_buffer_create", |b| {
|
||||
b.iter(|| {
|
||||
let buffer = SyndromeBuffer::new(1024);
|
||||
black_box(buffer);
|
||||
});
|
||||
});
|
||||
|
||||
// Large buffer sizes
|
||||
for size in [1024, 4096, 16384, 65536].iter() {
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("syndrome_buffer_create", size),
|
||||
size,
|
||||
|b, &sz| {
|
||||
b.iter(|| {
|
||||
let buffer = SyndromeBuffer::new(sz);
|
||||
black_box(buffer);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// MEMORY BANDWIDTH BENCHMARKS
|
||||
// ============================================================================
|
||||
|
||||
/// Benchmark memory bandwidth operations
|
||||
fn bench_memory_bandwidth(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("memory_bandwidth");
|
||||
|
||||
// Large data copy (TileReport array)
|
||||
group.throughput(Throughput::Bytes(
|
||||
255 * std::mem::size_of::<TileReport>() as u64,
|
||||
));
|
||||
group.bench_function("copy_255_reports", |b| {
|
||||
let source: Vec<TileReport> = (1..=255).map(|i| TileReport::new(i)).collect();
|
||||
|
||||
b.iter(|| {
|
||||
let copy: Vec<TileReport> = source.clone();
|
||||
black_box(copy);
|
||||
});
|
||||
});
|
||||
|
||||
// DetectorBitmap copy
|
||||
group.throughput(Throughput::Bytes(
|
||||
std::mem::size_of::<DetectorBitmap>() as u64
|
||||
));
|
||||
group.bench_function("copy_bitmap", |b| {
|
||||
let mut bitmap = DetectorBitmap::new(1024);
|
||||
for i in 0..512 {
|
||||
bitmap.set(i, true);
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
let copy = bitmap;
|
||||
black_box(copy);
|
||||
});
|
||||
});
|
||||
|
||||
// Batch bitmap copy
|
||||
group.throughput(Throughput::Bytes(
|
||||
100 * std::mem::size_of::<DetectorBitmap>() as u64,
|
||||
));
|
||||
group.bench_function("copy_100_bitmaps", |b| {
|
||||
let bitmaps: Vec<DetectorBitmap> = (0..100)
|
||||
.map(|i| {
|
||||
let mut bm = DetectorBitmap::new(1024);
|
||||
bm.set(i * 10, true);
|
||||
bm
|
||||
})
|
||||
.collect();
|
||||
|
||||
b.iter(|| {
|
||||
let copy: Vec<DetectorBitmap> = bitmaps.clone();
|
||||
black_box(copy);
|
||||
});
|
||||
});
|
||||
|
||||
// SyndromeRound copy
|
||||
group.throughput(Throughput::Bytes(
|
||||
std::mem::size_of::<SyndromeRound>() as u64
|
||||
));
|
||||
group.bench_function("copy_syndrome_round", |b| {
|
||||
let mut detectors = DetectorBitmap::new(256);
|
||||
for i in 0..25 {
|
||||
detectors.set(i * 10, true);
|
||||
}
|
||||
let round = SyndromeRound::new(12345, 100, 1000000, detectors, 0);
|
||||
|
||||
b.iter(|| {
|
||||
let copy = round.clone();
|
||||
black_box(copy);
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// CRITERION GROUPS
|
||||
// ============================================================================
|
||||
|
||||
criterion_group!(
|
||||
memory_benches,
|
||||
bench_structure_sizes,
|
||||
bench_per_tile_memory,
|
||||
bench_allocation_free_ops,
|
||||
bench_cache_efficiency,
|
||||
bench_memory_pool,
|
||||
bench_heap_allocations,
|
||||
bench_memory_bandwidth,
|
||||
);
|
||||
|
||||
criterion_main!(memory_benches);
|
||||
164
vendor/ruvector/crates/ruQu/benches/mincut_bench.rs
vendored
Normal file
164
vendor/ruvector/crates/ruQu/benches/mincut_bench.rs
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
//! Benchmarks for the real SubpolynomialMinCut integration
|
||||
//!
|
||||
//! Tests the El-Hayek/Henzinger/Li O(n^{o(1)}) algorithm performance.
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
|
||||
use ruqu::mincut::DynamicMinCutEngine;
|
||||
|
||||
/// Benchmark min-cut engine creation
|
||||
fn bench_engine_creation(c: &mut Criterion) {
|
||||
c.bench_function("mincut_engine_creation", |b| {
|
||||
b.iter(|| black_box(DynamicMinCutEngine::new()));
|
||||
});
|
||||
}
|
||||
|
||||
/// Benchmark edge insertion
|
||||
fn bench_edge_insertion(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("mincut_edge_insertion");
|
||||
|
||||
for size in [10, 50, 100, 500] {
|
||||
group.throughput(Throughput::Elements(size as u64));
|
||||
group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, &size| {
|
||||
b.iter_batched(
|
||||
|| DynamicMinCutEngine::new(),
|
||||
|mut engine| {
|
||||
for i in 0..size {
|
||||
engine.insert_edge(i as u32, (i + 1) as u32, 1.0);
|
||||
}
|
||||
black_box(engine)
|
||||
},
|
||||
criterion::BatchSize::SmallInput,
|
||||
);
|
||||
});
|
||||
}
|
||||
group.finish();
|
||||
}
|
||||
|
||||
/// Benchmark min-cut query after building a graph
|
||||
fn bench_mincut_query(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("mincut_query");
|
||||
|
||||
for size in [10, 50, 100, 200] {
|
||||
group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, &size| {
|
||||
// Build a random-ish graph
|
||||
let mut engine = DynamicMinCutEngine::new();
|
||||
for i in 0..size {
|
||||
engine.insert_edge(i as u32, ((i + 1) % size) as u32, 1.0);
|
||||
if i > 0 {
|
||||
engine.insert_edge(i as u32, ((i + size / 2) % size) as u32, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
b.iter(|| black_box(engine.min_cut_value()));
|
||||
});
|
||||
}
|
||||
group.finish();
|
||||
}
|
||||
|
||||
/// Benchmark dynamic updates (insert + query)
|
||||
fn bench_dynamic_updates(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("mincut_dynamic_updates");
|
||||
|
||||
for size in [50, 100] {
|
||||
group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, &size| {
|
||||
// Build initial graph
|
||||
let mut engine = DynamicMinCutEngine::new();
|
||||
for i in 0..size {
|
||||
engine.insert_edge(i as u32, ((i + 1) % size) as u32, 1.0);
|
||||
}
|
||||
// Query once to prime
|
||||
let _ = engine.min_cut_value();
|
||||
|
||||
let mut counter = 0u32;
|
||||
b.iter(|| {
|
||||
// Insert edge
|
||||
engine.insert_edge(counter % size as u32, (counter + 10) % size as u32, 1.5);
|
||||
// Query
|
||||
let cut = engine.min_cut_value();
|
||||
// Delete edge
|
||||
engine.delete_edge(counter % size as u32, (counter + 10) % size as u32);
|
||||
counter = counter.wrapping_add(1);
|
||||
black_box(cut)
|
||||
});
|
||||
});
|
||||
}
|
||||
group.finish();
|
||||
}
|
||||
|
||||
/// Benchmark grid graph (surface code-like)
|
||||
fn bench_surface_code_graph(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("mincut_surface_code");
|
||||
|
||||
for distance in [5, 7, 9] {
|
||||
let num_qubits = 2 * distance * distance - 2 * distance + 1;
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("distance", distance),
|
||||
&distance,
|
||||
|b, &d| {
|
||||
b.iter_batched(
|
||||
|| {
|
||||
// Build a grid graph approximating surface code
|
||||
let mut engine = DynamicMinCutEngine::new();
|
||||
for row in 0..d {
|
||||
for col in 0..d {
|
||||
let v = (row * d + col) as u32;
|
||||
// Horizontal edges
|
||||
if col + 1 < d {
|
||||
engine.insert_edge(v, v + 1, 1.0);
|
||||
}
|
||||
// Vertical edges
|
||||
if row + 1 < d {
|
||||
engine.insert_edge(v, v + d as u32, 1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
engine
|
||||
},
|
||||
|mut engine| {
|
||||
// Simulate syndrome updates
|
||||
for i in 0..10 {
|
||||
let v = (i % (d * d)) as u32;
|
||||
engine.insert_edge(v, v + 1, 0.8);
|
||||
let _ = engine.min_cut_value();
|
||||
}
|
||||
black_box(engine)
|
||||
},
|
||||
criterion::BatchSize::SmallInput,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
group.finish();
|
||||
}
|
||||
|
||||
/// Benchmark full min-cut result with certificate
|
||||
fn bench_mincut_certified(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("mincut_certified");
|
||||
|
||||
for size in [50, 100] {
|
||||
group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, &size| {
|
||||
let mut engine = DynamicMinCutEngine::new();
|
||||
for i in 0..size {
|
||||
engine.insert_edge(i as u32, ((i + 1) % size) as u32, 1.0);
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
let result = engine.min_cut();
|
||||
black_box((result.value, result.is_exact, result.witness_hash))
|
||||
});
|
||||
});
|
||||
}
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
benches,
|
||||
bench_engine_creation,
|
||||
bench_edge_insertion,
|
||||
bench_mincut_query,
|
||||
bench_dynamic_updates,
|
||||
bench_surface_code_graph,
|
||||
bench_mincut_certified,
|
||||
);
|
||||
|
||||
criterion_main!(benches);
|
||||
586
vendor/ruvector/crates/ruQu/benches/scaling_bench.rs
vendored
Normal file
586
vendor/ruvector/crates/ruQu/benches/scaling_bench.rs
vendored
Normal file
@@ -0,0 +1,586 @@
|
||||
//! Scaling benchmarks for ruQu Coherence Gate.
|
||||
//!
|
||||
//! Measures how performance scales with:
|
||||
//! - Code distance (5, 9, 13, 17, 21)
|
||||
//! - Qubit count (50, 100, 500, 1000)
|
||||
//! - Tile count (10, 50, 100, 255)
|
||||
//! - Graph density
|
||||
//!
|
||||
//! Run with: `cargo bench -p ruqu --bench scaling_bench`
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
|
||||
use std::hint::black_box as hint_black_box;
|
||||
|
||||
use ruqu::filters::{FilterConfig, FilterPipeline, SystemState};
|
||||
use ruqu::syndrome::{DetectorBitmap, SyndromeBuffer, SyndromeRound};
|
||||
use ruqu::tile::{GateThresholds, PatchGraph, SyndromeDelta, TileReport, TileZero, WorkerTile};
|
||||
|
||||
// ============================================================================
|
||||
// HELPER FUNCTIONS
|
||||
// ============================================================================
|
||||
|
||||
/// Calculate approximate detector count for a surface code distance
|
||||
fn detectors_for_distance(distance: usize) -> usize {
|
||||
// For surface code, detector count is roughly d^2
|
||||
distance * distance
|
||||
}
|
||||
|
||||
/// Calculate approximate qubit count for a surface code distance
|
||||
fn qubits_for_distance(distance: usize) -> usize {
|
||||
// For surface code, data qubits = 2*d^2 - 2*d + 1
|
||||
2 * distance * distance - 2 * distance + 1
|
||||
}
|
||||
|
||||
/// Create a worker tile sized for a given qubit count
|
||||
fn create_scaled_worker_tile(tile_id: u8, qubit_count: usize) -> WorkerTile {
|
||||
let mut tile = WorkerTile::new(tile_id);
|
||||
|
||||
let vertices = (qubit_count / 4).min(255) as u16; // Tile handles a fraction of qubits
|
||||
let edges_per_vertex = 4; // Surface code connectivity
|
||||
|
||||
for i in 0..vertices {
|
||||
tile.patch_graph.ensure_vertex(i);
|
||||
}
|
||||
|
||||
let mut edges_added = 0u16;
|
||||
let max_edges = (vertices as usize * edges_per_vertex / 2).min(1000) as u16;
|
||||
|
||||
'outer: for i in 0..vertices.saturating_sub(1) {
|
||||
// Lattice-like connectivity
|
||||
let neighbors = [i + 1, i.wrapping_add(vertices / 10)];
|
||||
for &neighbor in &neighbors {
|
||||
if neighbor < vertices && neighbor != i && edges_added < max_edges {
|
||||
if tile.patch_graph.add_edge(i, neighbor, 1000).is_some() {
|
||||
edges_added += 1;
|
||||
}
|
||||
}
|
||||
if edges_added >= max_edges {
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tile.patch_graph.recompute_components();
|
||||
tile
|
||||
}
|
||||
|
||||
/// Create a filter pipeline sized for a given qubit count
|
||||
fn create_scaled_filter_pipeline(qubit_count: usize) -> FilterPipeline {
|
||||
let config = FilterConfig::default();
|
||||
let mut pipeline = FilterPipeline::new(config);
|
||||
|
||||
let vertices = qubit_count.min(500) as u64;
|
||||
|
||||
// Add graph structure proportional to qubit count
|
||||
for i in 0..vertices.saturating_sub(1) {
|
||||
let _ = pipeline.structural_mut().insert_edge(i, i + 1, 1.0);
|
||||
if i % 10 == 0 && i + 10 < vertices {
|
||||
let _ = pipeline.structural_mut().insert_edge(i, i + 10, 0.5);
|
||||
}
|
||||
}
|
||||
pipeline.structural_mut().build();
|
||||
|
||||
// Warm up shift filter
|
||||
let num_regions = (qubit_count / 16).min(64);
|
||||
for region in 0..num_regions {
|
||||
for _ in 0..50 {
|
||||
pipeline.shift_mut().update(region, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
// Warm up evidence filter
|
||||
for _ in 0..20 {
|
||||
pipeline.evidence_mut().update(1.5);
|
||||
}
|
||||
|
||||
pipeline
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// LATENCY VS CODE DISTANCE
|
||||
// ============================================================================
|
||||
|
||||
/// Benchmark latency scaling with code distance
|
||||
fn bench_latency_vs_distance(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("latency_vs_distance");
|
||||
group.sample_size(100);
|
||||
|
||||
let distances = [5, 9, 13, 17, 21];
|
||||
|
||||
for distance in distances.iter() {
|
||||
let qubit_count = qubits_for_distance(*distance);
|
||||
let detector_count = detectors_for_distance(*distance);
|
||||
|
||||
// Worker tile tick latency
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("worker_tick", format!("d{}", distance)),
|
||||
&qubit_count,
|
||||
|b, &qubits| {
|
||||
let mut tile = create_scaled_worker_tile(1, qubits);
|
||||
let delta = SyndromeDelta::new(0, 1, 100);
|
||||
|
||||
b.iter(|| {
|
||||
let report = tile.tick(black_box(&delta));
|
||||
black_box(report)
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// Filter pipeline evaluation latency
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("filter_pipeline", format!("d{}", distance)),
|
||||
&qubit_count,
|
||||
|b, &qubits| {
|
||||
let pipeline = create_scaled_filter_pipeline(qubits);
|
||||
let state = SystemState::new(qubits);
|
||||
|
||||
b.iter(|| {
|
||||
let result = pipeline.evaluate(black_box(&state));
|
||||
black_box(result)
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// Full decision cycle latency
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("full_decision", format!("d{}", distance)),
|
||||
&qubit_count,
|
||||
|b, &qubits| {
|
||||
let mut tile = create_scaled_worker_tile(1, qubits);
|
||||
let thresholds = GateThresholds::default();
|
||||
let mut tilezero = TileZero::new(thresholds);
|
||||
|
||||
b.iter(|| {
|
||||
let delta = SyndromeDelta::new(0, 1, 100);
|
||||
let report = tile.tick(&delta);
|
||||
let reports = vec![report; 10];
|
||||
let decision = tilezero.merge_reports(reports);
|
||||
black_box(decision)
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// Syndrome buffer push latency
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("syndrome_push", format!("d{}", distance)),
|
||||
&detector_count,
|
||||
|b, &detectors| {
|
||||
let mut buffer = SyndromeBuffer::new(1024);
|
||||
let mut round_id = 0u64;
|
||||
|
||||
b.iter(|| {
|
||||
let mut bitmap = DetectorBitmap::new(detectors.min(1024));
|
||||
for i in 0..detectors.min(1024) / 10 {
|
||||
bitmap.set(i * 10, true);
|
||||
}
|
||||
let round = SyndromeRound::new(round_id, round_id, round_id * 1000, bitmap, 0);
|
||||
buffer.push(round);
|
||||
round_id += 1;
|
||||
black_box(buffer.len())
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// LATENCY VS QUBIT COUNT
|
||||
// ============================================================================
|
||||
|
||||
/// Benchmark latency scaling with qubit count
|
||||
fn bench_latency_vs_qubit_count(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("latency_vs_qubits");
|
||||
group.sample_size(100);
|
||||
|
||||
let qubit_counts = [50, 100, 500, 1000];
|
||||
|
||||
for qubit_count in qubit_counts.iter() {
|
||||
// Worker tile tick latency
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("worker_tick", format!("q{}", qubit_count)),
|
||||
qubit_count,
|
||||
|b, &qubits| {
|
||||
let mut tile = create_scaled_worker_tile(1, qubits);
|
||||
let delta = SyndromeDelta::new(0, 1, 100);
|
||||
|
||||
b.iter(|| {
|
||||
let report = tile.tick(black_box(&delta));
|
||||
black_box(report)
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// Filter pipeline evaluation
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("filter_pipeline", format!("q{}", qubit_count)),
|
||||
qubit_count,
|
||||
|b, &qubits| {
|
||||
let pipeline = create_scaled_filter_pipeline(qubits);
|
||||
let state = SystemState::new(qubits);
|
||||
|
||||
b.iter(|| {
|
||||
let result = pipeline.evaluate(black_box(&state));
|
||||
black_box(result)
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// Patch graph operations
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("patch_graph_estimate_cut", format!("q{}", qubit_count)),
|
||||
qubit_count,
|
||||
|b, &qubits| {
|
||||
let tile = create_scaled_worker_tile(1, qubits);
|
||||
|
||||
b.iter(|| {
|
||||
let cut = tile.patch_graph.estimate_local_cut();
|
||||
black_box(cut)
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// Component recomputation
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("recompute_components", format!("q{}", qubit_count)),
|
||||
qubit_count,
|
||||
|b, &qubits| {
|
||||
let mut tile = create_scaled_worker_tile(1, qubits);
|
||||
|
||||
b.iter(|| {
|
||||
tile.patch_graph.status |= PatchGraph::STATUS_DIRTY;
|
||||
let count = tile.patch_graph.recompute_components();
|
||||
black_box(count)
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// LATENCY VS TILE COUNT
|
||||
// ============================================================================
|
||||
|
||||
/// Benchmark latency scaling with tile count (TileZero merge)
|
||||
fn bench_latency_vs_tile_count(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("latency_vs_tiles");
|
||||
group.sample_size(100);
|
||||
|
||||
let tile_counts = [10, 50, 100, 150, 200, 255];
|
||||
|
||||
for tile_count in tile_counts.iter() {
|
||||
// TileZero merge latency
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("tilezero_merge", format!("t{}", tile_count)),
|
||||
tile_count,
|
||||
|b, &count| {
|
||||
let thresholds = GateThresholds::default();
|
||||
let mut tilezero = TileZero::new(thresholds);
|
||||
|
||||
let reports: Vec<TileReport> = (1..=count)
|
||||
.map(|i| {
|
||||
let mut report = TileReport::new(i as u8);
|
||||
report.local_cut = 10.0 + (i as f64 * 0.1);
|
||||
report.shift_score = 0.1;
|
||||
report.e_value = 200.0;
|
||||
report.num_vertices = 100;
|
||||
report.num_edges = 200;
|
||||
report
|
||||
})
|
||||
.collect();
|
||||
|
||||
b.iter(|| {
|
||||
let decision = tilezero.merge_reports(black_box(reports.clone()));
|
||||
black_box(decision)
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// Full decision cycle with scaled tiles
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("full_decision", format!("t{}", tile_count)),
|
||||
tile_count,
|
||||
|b, &count| {
|
||||
let mut tile = create_scaled_worker_tile(1, 100);
|
||||
let thresholds = GateThresholds::default();
|
||||
let mut tilezero = TileZero::new(thresholds);
|
||||
|
||||
b.iter(|| {
|
||||
let delta = SyndromeDelta::new(0, 1, 100);
|
||||
let report = tile.tick(&delta);
|
||||
let reports = vec![report; count];
|
||||
let decision = tilezero.merge_reports(reports);
|
||||
black_box(decision)
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// THROUGHPUT VS SYSTEM SIZE
|
||||
// ============================================================================
|
||||
|
||||
/// Benchmark throughput scaling with system size
|
||||
fn bench_throughput_vs_size(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("throughput_vs_size");
|
||||
|
||||
let qubit_counts = [50, 100, 500, 1000];
|
||||
|
||||
for qubit_count in qubit_counts.iter() {
|
||||
// Syndrome ingestion throughput
|
||||
group.throughput(Throughput::Elements(1000));
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("syndrome_ingestion", format!("q{}", qubit_count)),
|
||||
qubit_count,
|
||||
|b, &qubits| {
|
||||
let mut buffer = SyndromeBuffer::new(4096);
|
||||
let detector_count = (qubits / 2).min(1024);
|
||||
let mut round_id = 0u64;
|
||||
|
||||
b.iter(|| {
|
||||
for _ in 0..1000 {
|
||||
let mut bitmap = DetectorBitmap::new(detector_count);
|
||||
for i in 0..detector_count / 10 {
|
||||
bitmap.set(i * 10, true);
|
||||
}
|
||||
let round =
|
||||
SyndromeRound::new(round_id, round_id, round_id * 1000, bitmap, 0);
|
||||
buffer.push(round);
|
||||
round_id += 1;
|
||||
}
|
||||
black_box(buffer.len())
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// Decision throughput
|
||||
group.throughput(Throughput::Elements(100));
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("decision_throughput", format!("q{}", qubit_count)),
|
||||
qubit_count,
|
||||
|b, &qubits| {
|
||||
let mut tile = create_scaled_worker_tile(1, qubits);
|
||||
let thresholds = GateThresholds::default();
|
||||
let mut tilezero = TileZero::new(thresholds);
|
||||
|
||||
b.iter(|| {
|
||||
for i in 0..100 {
|
||||
let delta = SyndromeDelta::new(0, 1, (i % 256) as u16);
|
||||
let report = tile.tick(&delta);
|
||||
let reports = vec![report; 10];
|
||||
let decision = tilezero.merge_reports(reports);
|
||||
hint_black_box(decision);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// GRAPH DENSITY SCALING
|
||||
// ============================================================================
|
||||
|
||||
/// Benchmark latency scaling with graph density
|
||||
fn bench_latency_vs_density(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("latency_vs_density");
|
||||
group.sample_size(100);
|
||||
|
||||
let base_vertices = 100u16;
|
||||
let densities = [
|
||||
("sparse", base_vertices / 2), // 0.5 edges per vertex
|
||||
("linear", base_vertices), // 1 edge per vertex
|
||||
("lattice", base_vertices * 2), // 2 edges per vertex
|
||||
("dense", base_vertices * 4), // 4 edges per vertex
|
||||
("very_dense", base_vertices * 8), // 8 edges per vertex
|
||||
];
|
||||
|
||||
for (name, edge_count) in densities.iter() {
|
||||
// Worker tile tick
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("worker_tick", *name),
|
||||
edge_count,
|
||||
|b, &edges| {
|
||||
let mut tile = WorkerTile::new(1);
|
||||
|
||||
for i in 0..base_vertices {
|
||||
tile.patch_graph.ensure_vertex(i);
|
||||
}
|
||||
|
||||
let mut added = 0u16;
|
||||
'outer: for i in 0..base_vertices {
|
||||
for j in (i + 1)..base_vertices.min(i + 10) {
|
||||
if added >= edges {
|
||||
break 'outer;
|
||||
}
|
||||
if tile.patch_graph.add_edge(i, j, 1000).is_some() {
|
||||
added += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
tile.patch_graph.recompute_components();
|
||||
|
||||
let delta = SyndromeDelta::new(0, 1, 100);
|
||||
|
||||
b.iter(|| {
|
||||
let report = tile.tick(black_box(&delta));
|
||||
black_box(report)
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// Local cut estimation
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("estimate_local_cut", *name),
|
||||
edge_count,
|
||||
|b, &edges| {
|
||||
let mut graph = PatchGraph::new();
|
||||
|
||||
for i in 0..base_vertices {
|
||||
graph.ensure_vertex(i);
|
||||
}
|
||||
|
||||
let mut added = 0u16;
|
||||
'outer: for i in 0..base_vertices {
|
||||
for j in (i + 1)..base_vertices.min(i + 10) {
|
||||
if added >= edges {
|
||||
break 'outer;
|
||||
}
|
||||
if graph.add_edge(i, j, 1000).is_some() {
|
||||
added += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
graph.recompute_components();
|
||||
|
||||
b.iter(|| {
|
||||
let cut = graph.estimate_local_cut();
|
||||
black_box(cut)
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// Component recomputation
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("recompute_components", *name),
|
||||
edge_count,
|
||||
|b, &edges| {
|
||||
let mut graph = PatchGraph::new();
|
||||
|
||||
for i in 0..base_vertices {
|
||||
graph.ensure_vertex(i);
|
||||
}
|
||||
|
||||
let mut added = 0u16;
|
||||
'outer: for i in 0..base_vertices {
|
||||
for j in (i + 1)..base_vertices.min(i + 10) {
|
||||
if added >= edges {
|
||||
break 'outer;
|
||||
}
|
||||
if graph.add_edge(i, j, 1000).is_some() {
|
||||
added += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
graph.status |= PatchGraph::STATUS_DIRTY;
|
||||
let count = graph.recompute_components();
|
||||
black_box(count)
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// MEMORY PRESSURE SCALING
|
||||
// ============================================================================
|
||||
|
||||
/// Benchmark under memory pressure (large buffers)
|
||||
fn bench_memory_pressure(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("memory_pressure");
|
||||
group.sample_size(50);
|
||||
|
||||
let buffer_sizes = [1024, 4096, 16384, 65536];
|
||||
|
||||
for buffer_size in buffer_sizes.iter() {
|
||||
// Syndrome buffer under pressure
|
||||
group.throughput(Throughput::Elements(1000));
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("syndrome_buffer", format!("cap{}", buffer_size)),
|
||||
buffer_size,
|
||||
|b, &size| {
|
||||
let mut buffer = SyndromeBuffer::new(size);
|
||||
// Pre-fill to capacity
|
||||
for i in 0..(size as u64) {
|
||||
let round = SyndromeRound::new(i, i, i * 1000, DetectorBitmap::new(64), 0);
|
||||
buffer.push(round);
|
||||
}
|
||||
|
||||
let mut round_id = size as u64;
|
||||
b.iter(|| {
|
||||
for _ in 0..1000 {
|
||||
let round = SyndromeRound::new(
|
||||
round_id,
|
||||
round_id,
|
||||
round_id * 1000,
|
||||
DetectorBitmap::new(64),
|
||||
0,
|
||||
);
|
||||
buffer.push(round);
|
||||
round_id += 1;
|
||||
}
|
||||
black_box(buffer.len())
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// Window extraction under pressure
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("window_extraction", format!("cap{}", buffer_size)),
|
||||
buffer_size,
|
||||
|b, &size| {
|
||||
let mut buffer = SyndromeBuffer::new(size);
|
||||
for i in 0..(size as u64) {
|
||||
let round = SyndromeRound::new(i, i, i * 1000, DetectorBitmap::new(64), 0);
|
||||
buffer.push(round);
|
||||
}
|
||||
|
||||
let window_size = (size / 10).max(10);
|
||||
b.iter(|| {
|
||||
let window = buffer.window(window_size);
|
||||
black_box(window)
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// CRITERION GROUPS
|
||||
// ============================================================================
|
||||
|
||||
criterion_group!(
|
||||
scaling_benches,
|
||||
bench_latency_vs_distance,
|
||||
bench_latency_vs_qubit_count,
|
||||
bench_latency_vs_tile_count,
|
||||
bench_throughput_vs_size,
|
||||
bench_latency_vs_density,
|
||||
bench_memory_pressure,
|
||||
);
|
||||
|
||||
criterion_main!(scaling_benches);
|
||||
251
vendor/ruvector/crates/ruQu/benches/syndrome_bench.rs
vendored
Normal file
251
vendor/ruvector/crates/ruQu/benches/syndrome_bench.rs
vendored
Normal file
@@ -0,0 +1,251 @@
|
||||
//! Benchmarks for syndrome processing performance.
|
||||
//!
|
||||
//! Run with: `cargo bench -p ruqu`
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion, Throughput};
|
||||
use ruqu::syndrome::{DetectorBitmap, SyndromeBuffer, SyndromeDelta, SyndromeRound};
|
||||
|
||||
/// Benchmark DetectorBitmap operations
|
||||
fn bench_bitmap_operations(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("DetectorBitmap");
|
||||
|
||||
// Benchmark set operation
|
||||
group.throughput(Throughput::Elements(1024));
|
||||
group.bench_function("set_all_1024", |b| {
|
||||
let mut bitmap = DetectorBitmap::new(1024);
|
||||
b.iter(|| {
|
||||
for i in 0..1024 {
|
||||
bitmap.set(i, true);
|
||||
}
|
||||
black_box(&bitmap);
|
||||
});
|
||||
});
|
||||
|
||||
// Benchmark get operation
|
||||
group.bench_function("get_all_1024", |b| {
|
||||
let mut bitmap = DetectorBitmap::new(1024);
|
||||
for i in (0..1024).step_by(3) {
|
||||
bitmap.set(i, true);
|
||||
}
|
||||
b.iter(|| {
|
||||
let mut count = 0usize;
|
||||
for i in 0..1024 {
|
||||
if bitmap.get(i) {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
black_box(count);
|
||||
});
|
||||
});
|
||||
|
||||
// Benchmark popcount
|
||||
group.bench_function("popcount_sparse", |b| {
|
||||
let mut bitmap = DetectorBitmap::new(1024);
|
||||
for i in (0..1024).step_by(100) {
|
||||
bitmap.set(i, true);
|
||||
}
|
||||
b.iter(|| black_box(bitmap.popcount()));
|
||||
});
|
||||
|
||||
group.bench_function("popcount_dense", |b| {
|
||||
let mut bitmap = DetectorBitmap::new(1024);
|
||||
for i in 0..512 {
|
||||
bitmap.set(i, true);
|
||||
}
|
||||
b.iter(|| black_box(bitmap.popcount()));
|
||||
});
|
||||
|
||||
// Benchmark XOR
|
||||
group.bench_function("xor_1024", |b| {
|
||||
let mut a = DetectorBitmap::new(1024);
|
||||
let mut bb = DetectorBitmap::new(1024);
|
||||
for i in (0..512).step_by(2) {
|
||||
a.set(i, true);
|
||||
}
|
||||
for i in (256..768).step_by(2) {
|
||||
bb.set(i, true);
|
||||
}
|
||||
b.iter(|| black_box(a.xor(&bb)));
|
||||
});
|
||||
|
||||
// Benchmark iter_fired
|
||||
group.bench_function("iter_fired_sparse", |b| {
|
||||
let mut bitmap = DetectorBitmap::new(1024);
|
||||
for i in (0..1024).step_by(100) {
|
||||
bitmap.set(i, true);
|
||||
}
|
||||
b.iter(|| {
|
||||
let count: usize = bitmap.iter_fired().count();
|
||||
black_box(count);
|
||||
});
|
||||
});
|
||||
|
||||
group.bench_function("iter_fired_dense", |b| {
|
||||
let mut bitmap = DetectorBitmap::new(1024);
|
||||
for i in 0..100 {
|
||||
bitmap.set(i, true);
|
||||
}
|
||||
b.iter(|| {
|
||||
let count: usize = bitmap.iter_fired().count();
|
||||
black_box(count);
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
/// Benchmark SyndromeBuffer operations
|
||||
fn bench_buffer_operations(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("SyndromeBuffer");
|
||||
|
||||
// Benchmark push (main hot path)
|
||||
group.throughput(Throughput::Elements(1));
|
||||
group.bench_function("push", |b| {
|
||||
let mut buffer = SyndromeBuffer::new(1024);
|
||||
let mut round_id = 0u64;
|
||||
b.iter(|| {
|
||||
let mut detectors = DetectorBitmap::new(64);
|
||||
detectors.set((round_id % 64) as usize, true);
|
||||
let round = SyndromeRound::new(round_id, round_id, round_id * 1000, detectors, 0);
|
||||
buffer.push(round);
|
||||
round_id = round_id.wrapping_add(1);
|
||||
black_box(&buffer);
|
||||
});
|
||||
});
|
||||
|
||||
// Benchmark window extraction
|
||||
group.bench_function("window_10", |b| {
|
||||
let mut buffer = SyndromeBuffer::new(1024);
|
||||
for i in 0..1000 {
|
||||
let round = SyndromeRound::new(i, i, i * 1000, DetectorBitmap::new(64), 0);
|
||||
buffer.push(round);
|
||||
}
|
||||
b.iter(|| black_box(buffer.window(10)));
|
||||
});
|
||||
|
||||
group.bench_function("window_100", |b| {
|
||||
let mut buffer = SyndromeBuffer::new(1024);
|
||||
for i in 0..1000 {
|
||||
let round = SyndromeRound::new(i, i, i * 1000, DetectorBitmap::new(64), 0);
|
||||
buffer.push(round);
|
||||
}
|
||||
b.iter(|| black_box(buffer.window(100)));
|
||||
});
|
||||
|
||||
// Benchmark get by round_id
|
||||
group.bench_function("get_recent", |b| {
|
||||
let mut buffer = SyndromeBuffer::new(1024);
|
||||
for i in 0..1000 {
|
||||
let round = SyndromeRound::new(i, i, i * 1000, DetectorBitmap::new(64), 0);
|
||||
buffer.push(round);
|
||||
}
|
||||
b.iter(|| black_box(buffer.get(995)));
|
||||
});
|
||||
|
||||
group.bench_function("get_old", |b| {
|
||||
let mut buffer = SyndromeBuffer::new(1024);
|
||||
for i in 0..1000 {
|
||||
let round = SyndromeRound::new(i, i, i * 1000, DetectorBitmap::new(64), 0);
|
||||
buffer.push(round);
|
||||
}
|
||||
b.iter(|| black_box(buffer.get(100)));
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
/// Benchmark SyndromeDelta computation
|
||||
fn bench_delta_operations(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("SyndromeDelta");
|
||||
|
||||
// Create test rounds
|
||||
let mut d1 = DetectorBitmap::new(1024);
|
||||
let mut d2 = DetectorBitmap::new(1024);
|
||||
for i in (0..512).step_by(2) {
|
||||
d1.set(i, true);
|
||||
}
|
||||
for i in (256..768).step_by(2) {
|
||||
d2.set(i, true);
|
||||
}
|
||||
let round1 = SyndromeRound::new(1, 100, 1000, d1, 0);
|
||||
let round2 = SyndromeRound::new(2, 101, 2000, d2, 0);
|
||||
|
||||
// Benchmark delta computation
|
||||
group.bench_function("compute", |b| {
|
||||
b.iter(|| black_box(SyndromeDelta::compute(&round1, &round2)));
|
||||
});
|
||||
|
||||
// Benchmark activity level
|
||||
let delta = SyndromeDelta::compute(&round1, &round2);
|
||||
group.bench_function("activity_level", |b| {
|
||||
b.iter(|| black_box(delta.activity_level()));
|
||||
});
|
||||
|
||||
// Benchmark is_quiet
|
||||
group.bench_function("is_quiet", |b| {
|
||||
b.iter(|| black_box(delta.is_quiet()));
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
/// Benchmark full pipeline throughput
|
||||
fn bench_pipeline_throughput(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("Pipeline");
|
||||
group.throughput(Throughput::Elements(1000));
|
||||
|
||||
group.bench_function("ingest_1000_rounds", |b| {
|
||||
b.iter(|| {
|
||||
let mut buffer = SyndromeBuffer::new(1024);
|
||||
for i in 0..1000u64 {
|
||||
let mut detectors = DetectorBitmap::new(64);
|
||||
// Simulate sparse detector firings
|
||||
if i % 10 == 0 {
|
||||
detectors.set((i % 64) as usize, true);
|
||||
}
|
||||
let round = SyndromeRound::new(i, i, i * 1000, detectors, 0);
|
||||
buffer.push(round);
|
||||
}
|
||||
black_box(&buffer);
|
||||
});
|
||||
});
|
||||
|
||||
group.bench_function("ingest_and_delta_1000", |b| {
|
||||
b.iter(|| {
|
||||
let mut buffer = SyndromeBuffer::new(1024);
|
||||
let mut prev_round: Option<SyndromeRound> = None;
|
||||
let mut delta_count = 0usize;
|
||||
|
||||
for i in 0..1000u64 {
|
||||
let mut detectors = DetectorBitmap::new(64);
|
||||
if i % 10 == 0 {
|
||||
detectors.set((i % 64) as usize, true);
|
||||
}
|
||||
let round = SyndromeRound::new(i, i, i * 1000, detectors, 0);
|
||||
|
||||
if let Some(prev) = &prev_round {
|
||||
let delta = SyndromeDelta::compute(prev, &round);
|
||||
if !delta.is_quiet() {
|
||||
delta_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
prev_round = Some(round.clone());
|
||||
buffer.push(round);
|
||||
}
|
||||
black_box(delta_count);
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
benches,
|
||||
bench_bitmap_operations,
|
||||
bench_buffer_operations,
|
||||
bench_delta_operations,
|
||||
bench_pipeline_throughput,
|
||||
);
|
||||
|
||||
criterion_main!(benches);
|
||||
703
vendor/ruvector/crates/ruQu/benches/throughput_bench.rs
vendored
Normal file
703
vendor/ruvector/crates/ruQu/benches/throughput_bench.rs
vendored
Normal file
@@ -0,0 +1,703 @@
|
||||
//! Throughput benchmarks for ruQu Coherence Gate.
|
||||
//!
|
||||
//! Performance Targets:
|
||||
//! - Syndrome ingestion rate: **1M rounds/sec**
|
||||
//! - Gate decisions per second: **250K decisions/sec**
|
||||
//! - Permit token generation rate: **100K tokens/sec**
|
||||
//!
|
||||
//! Run with: `cargo bench -p ruqu --bench throughput_bench`
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
|
||||
|
||||
use ruqu::filters::{FilterConfig, FilterPipeline, SystemState};
|
||||
use ruqu::syndrome::{DetectorBitmap, SyndromeBuffer, SyndromeDelta, SyndromeRound};
|
||||
use ruqu::tile::{
|
||||
GateDecision, GateThresholds, PatchGraph, PermitToken, ReceiptLog,
|
||||
SyndromeDelta as TileSyndromeDelta, TileReport, TileZero, WorkerTile,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// HELPER FUNCTIONS
|
||||
// ============================================================================
|
||||
|
||||
/// Create a syndrome round with specified firing pattern
|
||||
fn create_syndrome_round(round_id: u64, detector_count: usize, firing_rate: f64) -> SyndromeRound {
|
||||
let mut detectors = DetectorBitmap::new(detector_count);
|
||||
let num_fired = ((detector_count as f64) * firing_rate) as usize;
|
||||
for i in 0..num_fired {
|
||||
detectors.set(i * (detector_count / num_fired.max(1)), true);
|
||||
}
|
||||
SyndromeRound::new(round_id, round_id, round_id * 1000, detectors, 0)
|
||||
}
|
||||
|
||||
/// Create a worker tile with pre-populated graph
|
||||
fn create_worker_tile(tile_id: u8, num_vertices: u16, num_edges: u16) -> WorkerTile {
|
||||
let mut tile = WorkerTile::new(tile_id);
|
||||
for i in 0..num_vertices.min(255) {
|
||||
tile.patch_graph.ensure_vertex(i);
|
||||
}
|
||||
let mut edges_added = 0u16;
|
||||
'outer: for i in 0..num_vertices.saturating_sub(1) {
|
||||
for j in (i + 1)..num_vertices.min(i + 4) {
|
||||
if edges_added >= num_edges {
|
||||
break 'outer;
|
||||
}
|
||||
if tile.patch_graph.add_edge(i, j, 1000).is_some() {
|
||||
edges_added += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
tile.patch_graph.recompute_components();
|
||||
tile
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SYNDROME INGESTION THROUGHPUT
|
||||
// ============================================================================
|
||||
|
||||
/// Benchmark syndrome ingestion rate (target: 1M rounds/sec)
|
||||
fn bench_syndrome_ingestion(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("syndrome_ingestion");
|
||||
|
||||
// Single round ingestion
|
||||
group.throughput(Throughput::Elements(1));
|
||||
group.bench_function("single_round", |b| {
|
||||
let mut buffer = SyndromeBuffer::new(4096);
|
||||
let mut round_id = 0u64;
|
||||
|
||||
b.iter(|| {
|
||||
let round = create_syndrome_round(round_id, 64, 0.1);
|
||||
buffer.push(round);
|
||||
round_id += 1;
|
||||
black_box(&buffer);
|
||||
});
|
||||
});
|
||||
|
||||
// Batch ingestion (1000 rounds)
|
||||
group.throughput(Throughput::Elements(1000));
|
||||
group.bench_function("batch_1000_rounds", |b| {
|
||||
let mut buffer = SyndromeBuffer::new(4096);
|
||||
let mut round_id = 0u64;
|
||||
|
||||
b.iter(|| {
|
||||
for _ in 0..1000 {
|
||||
let round = create_syndrome_round(round_id, 64, 0.1);
|
||||
buffer.push(round);
|
||||
round_id += 1;
|
||||
}
|
||||
black_box(&buffer);
|
||||
});
|
||||
});
|
||||
|
||||
// Large batch ingestion (10000 rounds)
|
||||
group.throughput(Throughput::Elements(10_000));
|
||||
group.bench_function("batch_10000_rounds", |b| {
|
||||
let mut buffer = SyndromeBuffer::new(16384);
|
||||
let mut round_id = 0u64;
|
||||
|
||||
b.iter(|| {
|
||||
for _ in 0..10_000 {
|
||||
let round = create_syndrome_round(round_id, 64, 0.1);
|
||||
buffer.push(round);
|
||||
round_id += 1;
|
||||
}
|
||||
black_box(&buffer);
|
||||
});
|
||||
});
|
||||
|
||||
// Varying detector counts
|
||||
for detector_count in [64, 256, 512, 1024].iter() {
|
||||
group.throughput(Throughput::Elements(1000));
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("batch_1000_detectors", detector_count),
|
||||
detector_count,
|
||||
|b, &count| {
|
||||
let mut buffer = SyndromeBuffer::new(4096);
|
||||
let mut round_id = 0u64;
|
||||
|
||||
b.iter(|| {
|
||||
for _ in 0..1000 {
|
||||
let round = create_syndrome_round(round_id, count, 0.1);
|
||||
buffer.push(round);
|
||||
round_id += 1;
|
||||
}
|
||||
black_box(&buffer);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Varying firing rates
|
||||
for firing_rate in [0.01, 0.05, 0.1, 0.25].iter() {
|
||||
group.throughput(Throughput::Elements(1000));
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new(
|
||||
"batch_1000_firing_rate",
|
||||
format!("{:.0}pct", firing_rate * 100.0),
|
||||
),
|
||||
firing_rate,
|
||||
|b, &rate| {
|
||||
let mut buffer = SyndromeBuffer::new(4096);
|
||||
let mut round_id = 0u64;
|
||||
|
||||
b.iter(|| {
|
||||
for _ in 0..1000 {
|
||||
let round = create_syndrome_round(round_id, 256, rate);
|
||||
buffer.push(round);
|
||||
round_id += 1;
|
||||
}
|
||||
black_box(&buffer);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// GATE DECISION THROUGHPUT
|
||||
// ============================================================================
|
||||
|
||||
/// Benchmark gate decisions per second
|
||||
fn bench_gate_decision_throughput(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("gate_decisions");
|
||||
|
||||
// Single decision
|
||||
group.throughput(Throughput::Elements(1));
|
||||
group.bench_function("single_decision", |b| {
|
||||
let mut tile = create_worker_tile(1, 64, 128);
|
||||
let thresholds = GateThresholds::default();
|
||||
let mut tilezero = TileZero::new(thresholds);
|
||||
|
||||
b.iter(|| {
|
||||
let delta = TileSyndromeDelta::new(0, 1, 100);
|
||||
let report = tile.tick(&delta);
|
||||
let reports = vec![report; 10];
|
||||
let decision = tilezero.merge_reports(reports);
|
||||
black_box(decision)
|
||||
});
|
||||
});
|
||||
|
||||
// Batch decisions (100)
|
||||
group.throughput(Throughput::Elements(100));
|
||||
group.bench_function("batch_100_decisions", |b| {
|
||||
let mut tile = create_worker_tile(1, 64, 128);
|
||||
let thresholds = GateThresholds::default();
|
||||
let mut tilezero = TileZero::new(thresholds);
|
||||
|
||||
b.iter(|| {
|
||||
for i in 0..100 {
|
||||
let delta = TileSyndromeDelta::new(0, 1, i as u16);
|
||||
let report = tile.tick(&delta);
|
||||
let reports = vec![report; 10];
|
||||
let decision = tilezero.merge_reports(reports);
|
||||
black_box(decision);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Batch decisions (1000)
|
||||
group.throughput(Throughput::Elements(1000));
|
||||
group.bench_function("batch_1000_decisions", |b| {
|
||||
let mut tile = create_worker_tile(1, 64, 128);
|
||||
let thresholds = GateThresholds::default();
|
||||
let mut tilezero = TileZero::new(thresholds);
|
||||
|
||||
b.iter(|| {
|
||||
for i in 0..1000 {
|
||||
let delta = TileSyndromeDelta::new(0, 1, (i % 256) as u16);
|
||||
let report = tile.tick(&delta);
|
||||
let reports = vec![report; 10];
|
||||
let decision = tilezero.merge_reports(reports);
|
||||
black_box(decision);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Decisions with varying tile counts
|
||||
for tile_count in [10, 50, 100, 255].iter() {
|
||||
group.throughput(Throughput::Elements(100));
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("batch_100_tile_count", tile_count),
|
||||
tile_count,
|
||||
|b, &count| {
|
||||
let mut tile = create_worker_tile(1, 64, 128);
|
||||
let thresholds = GateThresholds::default();
|
||||
let mut tilezero = TileZero::new(thresholds);
|
||||
|
||||
let base_reports: Vec<TileReport> = (1..=count)
|
||||
.map(|i| {
|
||||
let mut report = TileReport::new(i as u8);
|
||||
report.local_cut = 10.0;
|
||||
report.shift_score = 0.1;
|
||||
report.e_value = 200.0;
|
||||
report
|
||||
})
|
||||
.collect();
|
||||
|
||||
b.iter(|| {
|
||||
for _ in 0..100 {
|
||||
let delta = TileSyndromeDelta::new(0, 1, 100);
|
||||
let _ = tile.tick(&delta);
|
||||
let decision = tilezero.merge_reports(base_reports.clone());
|
||||
black_box(decision);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// PERMIT TOKEN GENERATION THROUGHPUT
|
||||
// ============================================================================
|
||||
|
||||
/// Benchmark permit token generation rate
|
||||
fn bench_permit_token_throughput(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("permit_tokens");
|
||||
|
||||
// Single token
|
||||
group.throughput(Throughput::Elements(1));
|
||||
group.bench_function("single_token", |b| {
|
||||
let thresholds = GateThresholds::default();
|
||||
let tilezero = TileZero::new(thresholds);
|
||||
let decision = GateDecision::Permit;
|
||||
|
||||
b.iter(|| {
|
||||
let token = tilezero.issue_permit(&decision);
|
||||
black_box(token)
|
||||
});
|
||||
});
|
||||
|
||||
// Batch tokens (1000)
|
||||
group.throughput(Throughput::Elements(1000));
|
||||
group.bench_function("batch_1000_tokens", |b| {
|
||||
let thresholds = GateThresholds::default();
|
||||
let tilezero = TileZero::new(thresholds);
|
||||
let decision = GateDecision::Permit;
|
||||
|
||||
b.iter(|| {
|
||||
for _ in 0..1000 {
|
||||
let token = tilezero.issue_permit(&decision);
|
||||
black_box(&token);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Token validation throughput
|
||||
group.throughput(Throughput::Elements(1000));
|
||||
group.bench_function("validate_1000_tokens", |b| {
|
||||
let thresholds = GateThresholds::default();
|
||||
let tilezero = TileZero::new(thresholds);
|
||||
let token = tilezero.issue_permit(&GateDecision::Permit);
|
||||
let now_ns = token.timestamp + 1000;
|
||||
|
||||
b.iter(|| {
|
||||
for _ in 0..1000 {
|
||||
let valid = token.is_valid(now_ns);
|
||||
black_box(valid);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// RECEIPT LOG THROUGHPUT
|
||||
// ============================================================================
|
||||
|
||||
/// Benchmark receipt log operations
|
||||
fn bench_receipt_log_throughput(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("receipt_log");
|
||||
|
||||
// Append throughput
|
||||
group.throughput(Throughput::Elements(1000));
|
||||
group.bench_function("append_1000", |b| {
|
||||
let mut log = ReceiptLog::new();
|
||||
let witness_hash = [0u8; 32];
|
||||
|
||||
b.iter(|| {
|
||||
for i in 0..1000 {
|
||||
log.append(GateDecision::Permit, i, i * 1000, witness_hash);
|
||||
}
|
||||
black_box(&log);
|
||||
});
|
||||
});
|
||||
|
||||
// Lookup throughput
|
||||
group.throughput(Throughput::Elements(1000));
|
||||
group.bench_function("lookup_1000", |b| {
|
||||
let mut log = ReceiptLog::new();
|
||||
let witness_hash = [0u8; 32];
|
||||
for i in 0..10000 {
|
||||
log.append(GateDecision::Permit, i, i * 1000, witness_hash);
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
for i in 0..1000 {
|
||||
let entry = log.get(i * 10);
|
||||
black_box(entry);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// WORKER TILE THROUGHPUT
|
||||
// ============================================================================
|
||||
|
||||
/// Benchmark worker tile tick throughput
|
||||
fn bench_worker_tile_throughput(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("worker_tile");
|
||||
|
||||
// Single tick
|
||||
group.throughput(Throughput::Elements(1));
|
||||
group.bench_function("single_tick", |b| {
|
||||
let mut tile = create_worker_tile(1, 64, 128);
|
||||
|
||||
b.iter(|| {
|
||||
let delta = TileSyndromeDelta::new(0, 1, 100);
|
||||
let report = tile.tick(&delta);
|
||||
black_box(report)
|
||||
});
|
||||
});
|
||||
|
||||
// Batch ticks (1000)
|
||||
group.throughput(Throughput::Elements(1000));
|
||||
group.bench_function("batch_1000_ticks", |b| {
|
||||
let mut tile = create_worker_tile(1, 64, 128);
|
||||
|
||||
b.iter(|| {
|
||||
for i in 0..1000 {
|
||||
let delta = TileSyndromeDelta::new(0, 1, (i % 256) as u16);
|
||||
let report = tile.tick(&delta);
|
||||
black_box(&report);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Sustained throughput (10000 ticks)
|
||||
group.throughput(Throughput::Elements(10_000));
|
||||
group.bench_function("sustained_10000_ticks", |b| {
|
||||
let mut tile = create_worker_tile(1, 64, 128);
|
||||
|
||||
b.iter(|| {
|
||||
for i in 0..10_000 {
|
||||
let delta = TileSyndromeDelta::new(0, 1, (i % 256) as u16);
|
||||
let report = tile.tick(&delta);
|
||||
black_box(&report);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Varying graph sizes
|
||||
for (vertices, edges) in [(32, 64), (64, 128), (128, 256), (200, 400)].iter() {
|
||||
group.throughput(Throughput::Elements(1000));
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("batch_1000_graph", format!("v{}e{}", vertices, edges)),
|
||||
&(*vertices, *edges),
|
||||
|b, &(v, e)| {
|
||||
let mut tile = create_worker_tile(1, v, e);
|
||||
|
||||
b.iter(|| {
|
||||
for i in 0..1000 {
|
||||
let delta = TileSyndromeDelta::new(0, 1, (i % 256) as u16);
|
||||
let report = tile.tick(&delta);
|
||||
black_box(&report);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// FILTER PIPELINE THROUGHPUT
|
||||
// ============================================================================
|
||||
|
||||
/// Benchmark filter pipeline throughput
|
||||
fn bench_filter_pipeline_throughput(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("filter_pipeline");
|
||||
|
||||
// Create a pre-warmed pipeline
|
||||
let create_pipeline = || {
|
||||
let config = FilterConfig::default();
|
||||
let mut pipeline = FilterPipeline::new(config);
|
||||
|
||||
for i in 0..50u64 {
|
||||
let _ = pipeline.structural_mut().insert_edge(i, i + 1, 1.0);
|
||||
}
|
||||
pipeline.structural_mut().build();
|
||||
|
||||
for region in 0..10 {
|
||||
for _ in 0..50 {
|
||||
pipeline.shift_mut().update(region, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
for _ in 0..20 {
|
||||
pipeline.evidence_mut().update(1.5);
|
||||
}
|
||||
|
||||
pipeline
|
||||
};
|
||||
|
||||
// Single evaluation
|
||||
group.throughput(Throughput::Elements(1));
|
||||
group.bench_function("single_evaluation", |b| {
|
||||
let pipeline = create_pipeline();
|
||||
let state = SystemState::new(100);
|
||||
|
||||
b.iter(|| {
|
||||
let result = pipeline.evaluate(&state);
|
||||
black_box(result)
|
||||
});
|
||||
});
|
||||
|
||||
// Batch evaluations (1000)
|
||||
group.throughput(Throughput::Elements(1000));
|
||||
group.bench_function("batch_1000_evaluations", |b| {
|
||||
let pipeline = create_pipeline();
|
||||
let state = SystemState::new(100);
|
||||
|
||||
b.iter(|| {
|
||||
for _ in 0..1000 {
|
||||
let result = pipeline.evaluate(&state);
|
||||
black_box(&result);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SYNDROME DELTA COMPUTATION THROUGHPUT
|
||||
// ============================================================================
|
||||
|
||||
/// Benchmark syndrome delta computation throughput
|
||||
fn bench_syndrome_delta_throughput(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("syndrome_delta");
|
||||
|
||||
// Create test rounds
|
||||
let create_rounds = |count: usize| -> Vec<SyndromeRound> {
|
||||
(0..count)
|
||||
.map(|i| create_syndrome_round(i as u64, 256, 0.1))
|
||||
.collect()
|
||||
};
|
||||
|
||||
// Single delta computation
|
||||
group.throughput(Throughput::Elements(1));
|
||||
group.bench_function("single_delta", |b| {
|
||||
let round1 = create_syndrome_round(0, 256, 0.1);
|
||||
let round2 = create_syndrome_round(1, 256, 0.1);
|
||||
|
||||
b.iter(|| {
|
||||
let delta = SyndromeDelta::compute(&round1, &round2);
|
||||
black_box(delta)
|
||||
});
|
||||
});
|
||||
|
||||
// Batch delta computation (1000)
|
||||
group.throughput(Throughput::Elements(999));
|
||||
group.bench_function("batch_1000_deltas", |b| {
|
||||
let rounds = create_rounds(1000);
|
||||
|
||||
b.iter(|| {
|
||||
for i in 0..999 {
|
||||
let delta = SyndromeDelta::compute(&rounds[i], &rounds[i + 1]);
|
||||
black_box(&delta);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Varying detector counts
|
||||
for detector_count in [64, 256, 512, 1024].iter() {
|
||||
group.throughput(Throughput::Elements(999));
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("batch_1000_detectors", detector_count),
|
||||
detector_count,
|
||||
|b, &count| {
|
||||
let rounds: Vec<SyndromeRound> = (0..1000)
|
||||
.map(|i| create_syndrome_round(i as u64, count, 0.1))
|
||||
.collect();
|
||||
|
||||
b.iter(|| {
|
||||
for i in 0..999 {
|
||||
let delta = SyndromeDelta::compute(&rounds[i], &rounds[i + 1]);
|
||||
black_box(&delta);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// PATCH GRAPH THROUGHPUT
|
||||
// ============================================================================
|
||||
|
||||
/// Benchmark patch graph operation throughput
|
||||
fn bench_patch_graph_throughput(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("patch_graph_throughput");
|
||||
|
||||
// Edge insertion throughput
|
||||
group.throughput(Throughput::Elements(1000));
|
||||
group.bench_function("insert_1000_edges", |b| {
|
||||
b.iter_batched(
|
||||
PatchGraph::new,
|
||||
|mut graph| {
|
||||
for i in 0..1000u16 {
|
||||
let v1 = i % 256;
|
||||
let v2 = (i + 1) % 256;
|
||||
if v1 != v2 {
|
||||
let _ = graph.add_edge(v1, v2, 1000);
|
||||
}
|
||||
}
|
||||
black_box(graph.num_edges)
|
||||
},
|
||||
criterion::BatchSize::SmallInput,
|
||||
);
|
||||
});
|
||||
|
||||
// Delta application throughput
|
||||
group.throughput(Throughput::Elements(1000));
|
||||
group.bench_function("apply_1000_deltas", |b| {
|
||||
b.iter_batched(
|
||||
|| {
|
||||
let mut graph = PatchGraph::new();
|
||||
for i in 0..100u16 {
|
||||
let _ = graph.add_edge(i, (i + 1) % 100, 1000);
|
||||
}
|
||||
graph
|
||||
},
|
||||
|mut graph| {
|
||||
for i in 0..1000u16 {
|
||||
let delta = TileSyndromeDelta::new(i % 100, (i + 1) % 100, 100);
|
||||
graph.apply_delta(&delta);
|
||||
}
|
||||
black_box(graph.num_edges)
|
||||
},
|
||||
criterion::BatchSize::SmallInput,
|
||||
);
|
||||
});
|
||||
|
||||
// Component recomputation throughput
|
||||
group.throughput(Throughput::Elements(100));
|
||||
group.bench_function("recompute_100_times", |b| {
|
||||
b.iter_batched(
|
||||
|| {
|
||||
let mut graph = PatchGraph::new();
|
||||
for i in 0..200u16 {
|
||||
let _ = graph.add_edge(i, (i + 1) % 200, 1000);
|
||||
}
|
||||
graph
|
||||
},
|
||||
|mut graph| {
|
||||
let mut count = 0u16;
|
||||
for _ in 0..100 {
|
||||
graph.status |= PatchGraph::STATUS_DIRTY;
|
||||
count = graph.recompute_components();
|
||||
}
|
||||
black_box(count)
|
||||
},
|
||||
criterion::BatchSize::SmallInput,
|
||||
);
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DETECTOR BITMAP THROUGHPUT
|
||||
// ============================================================================
|
||||
|
||||
/// Benchmark detector bitmap throughput
|
||||
fn bench_bitmap_throughput(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("bitmap_throughput");
|
||||
|
||||
// XOR throughput
|
||||
group.throughput(Throughput::Elements(1000));
|
||||
group.bench_function("xor_1000", |b| {
|
||||
let mut a = DetectorBitmap::new(1024);
|
||||
let mut bb = DetectorBitmap::new(1024);
|
||||
for i in (0..512).step_by(2) {
|
||||
a.set(i, true);
|
||||
}
|
||||
for i in (256..768).step_by(2) {
|
||||
bb.set(i, true);
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
for _ in 0..1000 {
|
||||
let result = a.xor(&bb);
|
||||
black_box(&result);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Popcount throughput
|
||||
group.throughput(Throughput::Elements(1000));
|
||||
group.bench_function("popcount_1000", |b| {
|
||||
let mut bitmap = DetectorBitmap::new(1024);
|
||||
for i in (0..512).step_by(2) {
|
||||
bitmap.set(i, true);
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
let mut total = 0usize;
|
||||
for _ in 0..1000 {
|
||||
total += bitmap.popcount();
|
||||
}
|
||||
black_box(total)
|
||||
});
|
||||
});
|
||||
|
||||
// Iterator throughput
|
||||
group.throughput(Throughput::Elements(1000));
|
||||
group.bench_function("iter_fired_1000", |b| {
|
||||
let mut bitmap = DetectorBitmap::new(1024);
|
||||
for i in 0..100 {
|
||||
bitmap.set(i * 10, true);
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
let mut total = 0usize;
|
||||
for _ in 0..1000 {
|
||||
total += bitmap.iter_fired().count();
|
||||
}
|
||||
black_box(total)
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// CRITERION GROUPS
|
||||
// ============================================================================
|
||||
|
||||
criterion_group!(
|
||||
throughput_benches,
|
||||
bench_syndrome_ingestion,
|
||||
bench_gate_decision_throughput,
|
||||
bench_permit_token_throughput,
|
||||
bench_receipt_log_throughput,
|
||||
bench_worker_tile_throughput,
|
||||
bench_filter_pipeline_throughput,
|
||||
bench_syndrome_delta_throughput,
|
||||
bench_patch_graph_throughput,
|
||||
bench_bitmap_throughput,
|
||||
);
|
||||
|
||||
criterion_main!(throughput_benches);
|
||||
Reference in New Issue
Block a user