630 lines
18 KiB
Rust
630 lines
18 KiB
Rust
//! Benchmarks for coherence gate evaluation
|
|
//!
|
|
//! ADR-014 Performance Target: < 500us per gate evaluation
|
|
//!
|
|
//! The gate is a deterministic decision point that:
|
|
//! 1. Evaluates current energy against thresholds
|
|
//! 2. Checks persistence history
|
|
//! 3. Determines compute lane (Reflex/Retrieval/Heavy/Human)
|
|
//! 4. Creates witness record
|
|
|
|
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
|
|
use std::collections::VecDeque;
|
|
use std::time::Duration;
|
|
|
|
// ============================================================================
|
|
// Types (Simulated for benchmarking)
|
|
// ============================================================================
|
|
|
|
/// Compute lanes for escalating complexity
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
|
pub enum ComputeLane {
|
|
/// Lane 0: Local residual updates (<1ms)
|
|
Reflex = 0,
|
|
/// Lane 1: Evidence fetching (~10ms)
|
|
Retrieval = 1,
|
|
/// Lane 2: Multi-step planning (~100ms)
|
|
Heavy = 2,
|
|
/// Lane 3: Human escalation
|
|
Human = 3,
|
|
}
|
|
|
|
/// Coherence energy snapshot
|
|
#[derive(Clone)]
|
|
pub struct CoherenceEnergy {
|
|
pub total_energy: f32,
|
|
pub scope_energies: Vec<(u64, f32)>, // (scope_id, energy)
|
|
pub timestamp: u64,
|
|
pub fingerprint: u64,
|
|
}
|
|
|
|
impl CoherenceEnergy {
|
|
pub fn new(total: f32, num_scopes: usize) -> Self {
|
|
let scope_energies: Vec<(u64, f32)> = (0..num_scopes)
|
|
.map(|i| (i as u64, total / num_scopes as f32))
|
|
.collect();
|
|
|
|
Self {
|
|
total_energy: total,
|
|
scope_energies,
|
|
timestamp: 0,
|
|
fingerprint: (total.to_bits() as u64).wrapping_mul(0x517cc1b727220a95),
|
|
}
|
|
}
|
|
|
|
pub fn scope_energy(&self, scope_id: u64) -> f32 {
|
|
self.scope_energies
|
|
.iter()
|
|
.find(|(id, _)| *id == scope_id)
|
|
.map(|(_, e)| *e)
|
|
.unwrap_or(0.0)
|
|
}
|
|
}
|
|
|
|
/// Action to be gated
|
|
#[derive(Clone)]
|
|
pub struct Action {
|
|
pub id: u64,
|
|
pub scope_id: u64,
|
|
pub action_type: ActionType,
|
|
pub payload_hash: u64,
|
|
}
|
|
|
|
#[derive(Clone, Copy)]
|
|
pub enum ActionType {
|
|
Read,
|
|
Write,
|
|
Execute,
|
|
External,
|
|
}
|
|
|
|
/// Threshold configuration
|
|
#[derive(Clone)]
|
|
pub struct ThresholdConfig {
|
|
pub reflex: f32,
|
|
pub retrieval: f32,
|
|
pub heavy: f32,
|
|
pub persistence_window_ms: u64,
|
|
}
|
|
|
|
impl Default for ThresholdConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
reflex: 0.1,
|
|
retrieval: 0.5,
|
|
heavy: 1.0,
|
|
persistence_window_ms: 5000,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Energy history for persistence detection
|
|
pub struct EnergyHistory {
|
|
/// Rolling window of (timestamp_ms, energy) pairs per scope
|
|
history: Vec<VecDeque<(u64, f32)>>,
|
|
max_scopes: usize,
|
|
window_size: usize,
|
|
}
|
|
|
|
impl EnergyHistory {
|
|
pub fn new(max_scopes: usize, window_size: usize) -> Self {
|
|
Self {
|
|
history: (0..max_scopes)
|
|
.map(|_| VecDeque::with_capacity(window_size))
|
|
.collect(),
|
|
max_scopes,
|
|
window_size,
|
|
}
|
|
}
|
|
|
|
pub fn record(&mut self, scope_id: u64, timestamp_ms: u64, energy: f32) {
|
|
if (scope_id as usize) < self.max_scopes {
|
|
let queue = &mut self.history[scope_id as usize];
|
|
if queue.len() >= self.window_size {
|
|
queue.pop_front();
|
|
}
|
|
queue.push_back((timestamp_ms, energy));
|
|
}
|
|
}
|
|
|
|
pub fn is_above_threshold(
|
|
&self,
|
|
scope_id: u64,
|
|
threshold: f32,
|
|
window_ms: u64,
|
|
current_time_ms: u64,
|
|
) -> bool {
|
|
if (scope_id as usize) >= self.max_scopes {
|
|
return false;
|
|
}
|
|
|
|
let queue = &self.history[scope_id as usize];
|
|
let cutoff = current_time_ms.saturating_sub(window_ms);
|
|
|
|
// Check if all samples in window are above threshold
|
|
let samples_in_window: Vec<_> = queue.iter().filter(|(ts, _)| *ts >= cutoff).collect();
|
|
|
|
if samples_in_window.is_empty() {
|
|
return false;
|
|
}
|
|
|
|
samples_in_window.iter().all(|(_, e)| *e >= threshold)
|
|
}
|
|
|
|
pub fn trend(&self, scope_id: u64, window_ms: u64, current_time_ms: u64) -> Option<f32> {
|
|
if (scope_id as usize) >= self.max_scopes {
|
|
return None;
|
|
}
|
|
|
|
let queue = &self.history[scope_id as usize];
|
|
let cutoff = current_time_ms.saturating_sub(window_ms);
|
|
|
|
let samples: Vec<_> = queue.iter().filter(|(ts, _)| *ts >= cutoff).collect();
|
|
|
|
if samples.len() < 2 {
|
|
return None;
|
|
}
|
|
|
|
// Simple linear trend: (last - first) / count
|
|
let first = samples.first().unwrap().1;
|
|
let last = samples.last().unwrap().1;
|
|
Some((last - first) / samples.len() as f32)
|
|
}
|
|
}
|
|
|
|
/// Witness record for audit
|
|
#[derive(Clone)]
|
|
pub struct WitnessRecord {
|
|
pub id: u64,
|
|
pub action_hash: u64,
|
|
pub energy_fingerprint: u64,
|
|
pub lane: ComputeLane,
|
|
pub allowed: bool,
|
|
pub timestamp: u64,
|
|
pub content_hash: u64,
|
|
}
|
|
|
|
impl WitnessRecord {
|
|
pub fn new(
|
|
action: &Action,
|
|
energy: &CoherenceEnergy,
|
|
lane: ComputeLane,
|
|
allowed: bool,
|
|
timestamp: u64,
|
|
) -> Self {
|
|
let content_hash = Self::compute_hash(action, energy, lane, allowed, timestamp);
|
|
|
|
Self {
|
|
id: timestamp, // Simplified
|
|
action_hash: action.payload_hash,
|
|
energy_fingerprint: energy.fingerprint,
|
|
lane,
|
|
allowed,
|
|
timestamp,
|
|
content_hash,
|
|
}
|
|
}
|
|
|
|
fn compute_hash(
|
|
action: &Action,
|
|
energy: &CoherenceEnergy,
|
|
lane: ComputeLane,
|
|
allowed: bool,
|
|
timestamp: u64,
|
|
) -> u64 {
|
|
// Simplified hash computation (in production: use Blake3)
|
|
let mut h = action.payload_hash;
|
|
h = h.wrapping_mul(0x517cc1b727220a95);
|
|
h ^= energy.fingerprint;
|
|
h = h.wrapping_mul(0x517cc1b727220a95);
|
|
h ^= (lane as u64) << 32 | (allowed as u64);
|
|
h = h.wrapping_mul(0x517cc1b727220a95);
|
|
h ^= timestamp;
|
|
h
|
|
}
|
|
}
|
|
|
|
/// Gate decision result
|
|
pub struct GateDecision {
|
|
pub allow: bool,
|
|
pub lane: ComputeLane,
|
|
pub witness: WitnessRecord,
|
|
pub denial_reason: Option<&'static str>,
|
|
}
|
|
|
|
/// Coherence gate
|
|
pub struct CoherenceGate {
|
|
pub config: ThresholdConfig,
|
|
pub history: EnergyHistory,
|
|
current_time_ms: u64,
|
|
}
|
|
|
|
impl CoherenceGate {
|
|
pub fn new(config: ThresholdConfig, max_scopes: usize) -> Self {
|
|
Self {
|
|
config,
|
|
history: EnergyHistory::new(max_scopes, 100),
|
|
current_time_ms: 0,
|
|
}
|
|
}
|
|
|
|
/// Evaluate whether action should proceed
|
|
pub fn evaluate(&mut self, action: &Action, energy: &CoherenceEnergy) -> GateDecision {
|
|
let current_energy = energy.scope_energy(action.scope_id);
|
|
|
|
// Record in history
|
|
self.history
|
|
.record(action.scope_id, self.current_time_ms, current_energy);
|
|
|
|
// Determine lane based on energy
|
|
let lane = if current_energy < self.config.reflex {
|
|
ComputeLane::Reflex
|
|
} else if current_energy < self.config.retrieval {
|
|
ComputeLane::Retrieval
|
|
} else if current_energy < self.config.heavy {
|
|
ComputeLane::Heavy
|
|
} else {
|
|
ComputeLane::Human
|
|
};
|
|
|
|
// Check for persistent incoherence
|
|
let persistent = self.history.is_above_threshold(
|
|
action.scope_id,
|
|
self.config.retrieval,
|
|
self.config.persistence_window_ms,
|
|
self.current_time_ms,
|
|
);
|
|
|
|
// Check for growing incoherence (trend)
|
|
let growing = self
|
|
.history
|
|
.trend(
|
|
action.scope_id,
|
|
self.config.persistence_window_ms,
|
|
self.current_time_ms,
|
|
)
|
|
.map(|t| t > 0.01)
|
|
.unwrap_or(false);
|
|
|
|
// Escalate if persistent and not already at high lane
|
|
let final_lane = if (persistent || growing) && lane < ComputeLane::Heavy {
|
|
ComputeLane::Heavy
|
|
} else {
|
|
lane
|
|
};
|
|
|
|
// Allow unless Human lane
|
|
let allow = final_lane < ComputeLane::Human;
|
|
|
|
let denial_reason = if !allow {
|
|
Some("Energy exceeds all automatic thresholds")
|
|
} else if persistent {
|
|
Some("Persistent incoherence - escalated")
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let witness = WitnessRecord::new(action, energy, final_lane, allow, self.current_time_ms);
|
|
|
|
self.current_time_ms += 1;
|
|
|
|
GateDecision {
|
|
allow,
|
|
lane: final_lane,
|
|
witness,
|
|
denial_reason,
|
|
}
|
|
}
|
|
|
|
/// Fast path evaluation (no history update)
|
|
#[inline]
|
|
pub fn evaluate_fast(&self, scope_energy: f32) -> ComputeLane {
|
|
if scope_energy < self.config.reflex {
|
|
ComputeLane::Reflex
|
|
} else if scope_energy < self.config.retrieval {
|
|
ComputeLane::Retrieval
|
|
} else if scope_energy < self.config.heavy {
|
|
ComputeLane::Heavy
|
|
} else {
|
|
ComputeLane::Human
|
|
}
|
|
}
|
|
|
|
/// Advance time (for benchmarking)
|
|
pub fn advance_time(&mut self, delta_ms: u64) {
|
|
self.current_time_ms += delta_ms;
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Benchmarks
|
|
// ============================================================================
|
|
|
|
/// Benchmark full gate evaluation
|
|
fn bench_gate_evaluate(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("gate_evaluate");
|
|
group.throughput(Throughput::Elements(1));
|
|
|
|
let config = ThresholdConfig::default();
|
|
let mut gate = CoherenceGate::new(config, 100);
|
|
|
|
let action = Action {
|
|
id: 1,
|
|
scope_id: 0,
|
|
action_type: ActionType::Write,
|
|
payload_hash: 0x12345678,
|
|
};
|
|
|
|
// Low energy (Reflex lane)
|
|
let low_energy = CoherenceEnergy::new(0.05, 10);
|
|
group.bench_function("low_energy_reflex", |b| {
|
|
b.iter(|| {
|
|
let decision = gate.evaluate(black_box(&action), black_box(&low_energy));
|
|
black_box(decision.lane)
|
|
})
|
|
});
|
|
|
|
// Medium energy (Retrieval lane)
|
|
let med_energy = CoherenceEnergy::new(0.3, 10);
|
|
group.bench_function("medium_energy_retrieval", |b| {
|
|
b.iter(|| {
|
|
let decision = gate.evaluate(black_box(&action), black_box(&med_energy));
|
|
black_box(decision.lane)
|
|
})
|
|
});
|
|
|
|
// High energy (Heavy lane)
|
|
let high_energy = CoherenceEnergy::new(0.8, 10);
|
|
group.bench_function("high_energy_heavy", |b| {
|
|
b.iter(|| {
|
|
let decision = gate.evaluate(black_box(&action), black_box(&high_energy));
|
|
black_box(decision.lane)
|
|
})
|
|
});
|
|
|
|
// Critical energy (Human lane)
|
|
let critical_energy = CoherenceEnergy::new(2.0, 10);
|
|
group.bench_function("critical_energy_human", |b| {
|
|
b.iter(|| {
|
|
let decision = gate.evaluate(black_box(&action), black_box(&critical_energy));
|
|
black_box(decision.lane)
|
|
})
|
|
});
|
|
|
|
group.finish();
|
|
}
|
|
|
|
/// Benchmark fast path evaluation (no history)
|
|
fn bench_gate_fast_path(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("gate_fast_path");
|
|
group.throughput(Throughput::Elements(1));
|
|
|
|
let config = ThresholdConfig::default();
|
|
let gate = CoherenceGate::new(config, 100);
|
|
|
|
for energy in [0.05, 0.3, 0.8, 2.0] {
|
|
group.bench_with_input(
|
|
BenchmarkId::new("evaluate_fast", format!("{:.2}", energy)),
|
|
&energy,
|
|
|b, &e| b.iter(|| black_box(gate.evaluate_fast(black_box(e)))),
|
|
);
|
|
}
|
|
|
|
group.finish();
|
|
}
|
|
|
|
/// Benchmark witness record creation
|
|
fn bench_witness_creation(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("gate_witness");
|
|
group.throughput(Throughput::Elements(1));
|
|
|
|
let action = Action {
|
|
id: 1,
|
|
scope_id: 0,
|
|
action_type: ActionType::Write,
|
|
payload_hash: 0x12345678,
|
|
};
|
|
let energy = CoherenceEnergy::new(0.3, 10);
|
|
|
|
group.bench_function("create_witness", |b| {
|
|
b.iter(|| {
|
|
WitnessRecord::new(
|
|
black_box(&action),
|
|
black_box(&energy),
|
|
black_box(ComputeLane::Retrieval),
|
|
black_box(true),
|
|
black_box(12345),
|
|
)
|
|
})
|
|
});
|
|
|
|
group.finish();
|
|
}
|
|
|
|
/// Benchmark history operations
|
|
fn bench_history_operations(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("gate_history");
|
|
|
|
let mut history = EnergyHistory::new(100, 1000);
|
|
|
|
// Pre-populate with some history
|
|
for t in 0..500 {
|
|
for scope in 0..10u64 {
|
|
history.record(scope, t, 0.3 + (t % 10) as f32 * 0.01);
|
|
}
|
|
}
|
|
|
|
// Record operation
|
|
group.bench_function("record_single", |b| {
|
|
let mut t = 1000u64;
|
|
b.iter(|| {
|
|
history.record(black_box(5), black_box(t), black_box(0.35));
|
|
t += 1;
|
|
})
|
|
});
|
|
|
|
// Check threshold
|
|
group.bench_function("check_threshold", |b| {
|
|
b.iter(|| {
|
|
history.is_above_threshold(black_box(5), black_box(0.3), black_box(100), black_box(500))
|
|
})
|
|
});
|
|
|
|
// Compute trend
|
|
group.bench_function("compute_trend", |b| {
|
|
b.iter(|| history.trend(black_box(5), black_box(100), black_box(500)))
|
|
});
|
|
|
|
group.finish();
|
|
}
|
|
|
|
/// Benchmark persistence detection with various window sizes
|
|
fn bench_persistence_detection(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("gate_persistence");
|
|
|
|
for window_size in [10, 100, 1000] {
|
|
let mut history = EnergyHistory::new(10, window_size);
|
|
|
|
// Fill history
|
|
for t in 0..window_size as u64 {
|
|
history.record(0, t, 0.4); // Consistently above retrieval threshold
|
|
}
|
|
|
|
group.bench_with_input(
|
|
BenchmarkId::new("check_persistent", window_size),
|
|
&window_size,
|
|
|b, &size| {
|
|
b.iter(|| {
|
|
history.is_above_threshold(
|
|
black_box(0),
|
|
black_box(0.3),
|
|
black_box(size as u64),
|
|
black_box(size as u64),
|
|
)
|
|
})
|
|
},
|
|
);
|
|
}
|
|
|
|
group.finish();
|
|
}
|
|
|
|
/// Benchmark batch evaluation (multiple actions)
|
|
fn bench_batch_evaluation(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("gate_batch");
|
|
|
|
let config = ThresholdConfig::default();
|
|
let mut gate = CoherenceGate::new(config, 100);
|
|
|
|
for batch_size in [10, 100, 1000] {
|
|
let actions: Vec<Action> = (0..batch_size)
|
|
.map(|i| Action {
|
|
id: i as u64,
|
|
scope_id: (i % 10) as u64,
|
|
action_type: ActionType::Write,
|
|
payload_hash: i as u64 * 0x517cc1b727220a95,
|
|
})
|
|
.collect();
|
|
|
|
let energies: Vec<CoherenceEnergy> = (0..batch_size)
|
|
.map(|i| CoherenceEnergy::new(0.1 + (i % 20) as f32 * 0.05, 10))
|
|
.collect();
|
|
|
|
group.throughput(Throughput::Elements(batch_size as u64));
|
|
group.bench_with_input(
|
|
BenchmarkId::new("evaluate_batch", batch_size),
|
|
&batch_size,
|
|
|b, _| {
|
|
b.iter(|| {
|
|
let mut lanes = Vec::with_capacity(actions.len());
|
|
for (action, energy) in actions.iter().zip(energies.iter()) {
|
|
let decision = gate.evaluate(action, energy);
|
|
lanes.push(decision.lane);
|
|
}
|
|
black_box(lanes)
|
|
})
|
|
},
|
|
);
|
|
}
|
|
|
|
group.finish();
|
|
}
|
|
|
|
/// Benchmark scope energy lookup
|
|
fn bench_scope_lookup(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("gate_scope_lookup");
|
|
|
|
for num_scopes in [10, 100, 1000] {
|
|
let energy = CoherenceEnergy::new(1.0, num_scopes);
|
|
|
|
group.bench_with_input(
|
|
BenchmarkId::new("lookup", num_scopes),
|
|
&num_scopes,
|
|
|b, &n| {
|
|
let scope_id = (n / 2) as u64;
|
|
b.iter(|| black_box(energy.scope_energy(black_box(scope_id))))
|
|
},
|
|
);
|
|
}
|
|
|
|
group.finish();
|
|
}
|
|
|
|
/// Benchmark threshold comparison patterns
|
|
fn bench_threshold_comparison(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("gate_threshold_cmp");
|
|
|
|
let config = ThresholdConfig::default();
|
|
|
|
// Sequential if-else (current implementation)
|
|
group.bench_function("sequential_if_else", |b| {
|
|
let energies: Vec<f32> = (0..1000).map(|i| (i as f32) * 0.002).collect();
|
|
b.iter(|| {
|
|
let mut lanes = [0u32; 4];
|
|
for &e in &energies {
|
|
let lane = if e < config.reflex {
|
|
0
|
|
} else if e < config.retrieval {
|
|
1
|
|
} else if e < config.heavy {
|
|
2
|
|
} else {
|
|
3
|
|
};
|
|
lanes[lane] += 1;
|
|
}
|
|
black_box(lanes)
|
|
})
|
|
});
|
|
|
|
// Binary search pattern
|
|
group.bench_function("binary_search", |b| {
|
|
let thresholds = [config.reflex, config.retrieval, config.heavy, f32::MAX];
|
|
let energies: Vec<f32> = (0..1000).map(|i| (i as f32) * 0.002).collect();
|
|
b.iter(|| {
|
|
let mut lanes = [0u32; 4];
|
|
for &e in &energies {
|
|
let lane = thresholds.partition_point(|&t| t <= e);
|
|
lanes[lane.min(3)] += 1;
|
|
}
|
|
black_box(lanes)
|
|
})
|
|
});
|
|
|
|
group.finish();
|
|
}
|
|
|
|
criterion_group!(
|
|
benches,
|
|
bench_gate_evaluate,
|
|
bench_gate_fast_path,
|
|
bench_witness_creation,
|
|
bench_history_operations,
|
|
bench_persistence_detection,
|
|
bench_batch_evaluation,
|
|
bench_scope_lookup,
|
|
bench_threshold_comparison,
|
|
);
|
|
|
|
criterion_main!(benches);
|