576 lines
18 KiB
Rust
576 lines
18 KiB
Rust
// Plaid ZK Proof & Learning Performance Benchmarks
|
|
//
|
|
// Run with: cargo bench --bench plaid_performance
|
|
//
|
|
// Expected results:
|
|
// - Proof generation: ~8μs per proof (32-bit range)
|
|
// - Transaction processing: ~1.5μs per transaction
|
|
// - Feature extraction: ~0.1μs
|
|
// - LSH hashing: ~0.05μs
|
|
|
|
use criterion::{black_box, criterion_group, criterion_main, Criterion, BenchmarkId, Throughput};
|
|
use ruvector_edge::plaid::*;
|
|
use ruvector_edge::plaid::zkproofs::{RangeProof, PedersenCommitment, FinancialProofBuilder};
|
|
use std::collections::HashMap;
|
|
|
|
// ============================================================================
|
|
// Proof Generation Benchmarks
|
|
// ============================================================================
|
|
|
|
fn bench_proof_generation(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("proof_generation");
|
|
|
|
// Test different range sizes (affects bit count and proof complexity)
|
|
for range_bits in [8, 16, 32, 64] {
|
|
let max = if range_bits == 64 {
|
|
u64::MAX / 2 // Avoid overflow
|
|
} else {
|
|
(1u64 << range_bits) - 1
|
|
};
|
|
let value = max / 2;
|
|
let blinding = PedersenCommitment::random_blinding();
|
|
|
|
group.throughput(Throughput::Elements(1));
|
|
|
|
group.bench_with_input(
|
|
BenchmarkId::new("range_proof", range_bits),
|
|
&(value, max, blinding),
|
|
|b, (v, m, bl)| {
|
|
b.iter(|| {
|
|
RangeProof::prove(
|
|
black_box(*v),
|
|
0,
|
|
black_box(*m),
|
|
bl,
|
|
)
|
|
});
|
|
},
|
|
);
|
|
}
|
|
|
|
group.finish();
|
|
}
|
|
|
|
fn bench_proof_verification(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("proof_verification");
|
|
|
|
// Pre-generate proofs of different sizes
|
|
let proofs: Vec<_> = [8, 16, 32, 64]
|
|
.iter()
|
|
.map(|&bits| {
|
|
let max = if bits == 64 {
|
|
u64::MAX / 2
|
|
} else {
|
|
(1u64 << bits) - 1
|
|
};
|
|
let value = max / 2;
|
|
let blinding = PedersenCommitment::random_blinding();
|
|
(bits, RangeProof::prove(value, 0, max, &blinding).unwrap())
|
|
})
|
|
.collect();
|
|
|
|
for (bits, proof) in &proofs {
|
|
group.throughput(Throughput::Elements(1));
|
|
|
|
group.bench_with_input(
|
|
BenchmarkId::new("verify", bits),
|
|
proof,
|
|
|b, p| {
|
|
b.iter(|| RangeProof::verify(black_box(p)));
|
|
},
|
|
);
|
|
}
|
|
|
|
group.finish();
|
|
}
|
|
|
|
fn bench_pedersen_commitment(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("pedersen_commitment");
|
|
|
|
let value = 50000u64;
|
|
let blinding = PedersenCommitment::random_blinding();
|
|
|
|
group.bench_function("commit", |b| {
|
|
b.iter(|| {
|
|
PedersenCommitment::commit(black_box(value), black_box(&blinding))
|
|
});
|
|
});
|
|
|
|
group.bench_function("verify_opening", |b| {
|
|
let commitment = PedersenCommitment::commit(value, &blinding);
|
|
b.iter(|| {
|
|
PedersenCommitment::verify_opening(
|
|
black_box(&commitment),
|
|
black_box(value),
|
|
black_box(&blinding),
|
|
)
|
|
});
|
|
});
|
|
|
|
group.finish();
|
|
}
|
|
|
|
fn bench_financial_proofs(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("financial_proofs");
|
|
|
|
let builder = FinancialProofBuilder::new()
|
|
.with_income(vec![6500, 6500, 6800, 6500])
|
|
.with_balances(vec![5000, 5200, 4800, 5100, 5300, 5000, 5500]);
|
|
|
|
group.bench_function("prove_income_above", |b| {
|
|
b.iter(|| {
|
|
builder.prove_income_above(black_box(5000))
|
|
});
|
|
});
|
|
|
|
group.bench_function("prove_affordability", |b| {
|
|
b.iter(|| {
|
|
builder.prove_affordability(black_box(2000), black_box(3))
|
|
});
|
|
});
|
|
|
|
group.bench_function("prove_no_overdrafts", |b| {
|
|
b.iter(|| {
|
|
builder.prove_no_overdrafts(black_box(30))
|
|
});
|
|
});
|
|
|
|
group.bench_function("prove_savings_above", |b| {
|
|
b.iter(|| {
|
|
builder.prove_savings_above(black_box(4000))
|
|
});
|
|
});
|
|
|
|
group.finish();
|
|
}
|
|
|
|
// ============================================================================
|
|
// Learning Algorithm Benchmarks
|
|
// ============================================================================
|
|
|
|
fn bench_feature_extraction(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("feature_extraction");
|
|
|
|
let tx = Transaction {
|
|
transaction_id: "tx123".to_string(),
|
|
account_id: "acc456".to_string(),
|
|
amount: 50.0,
|
|
date: "2024-03-15".to_string(),
|
|
name: "Starbucks Coffee Shop".to_string(),
|
|
merchant_name: Some("Starbucks".to_string()),
|
|
category: vec!["Food".to_string(), "Coffee".to_string()],
|
|
pending: false,
|
|
payment_channel: "in_store".to_string(),
|
|
};
|
|
|
|
group.throughput(Throughput::Elements(1));
|
|
|
|
group.bench_function("extract_features", |b| {
|
|
b.iter(|| extract_features(black_box(&tx)));
|
|
});
|
|
|
|
group.bench_function("to_embedding", |b| {
|
|
let features = extract_features(&tx);
|
|
b.iter(|| features.to_embedding());
|
|
});
|
|
|
|
group.bench_function("full_pipeline", |b| {
|
|
b.iter(|| {
|
|
let features = extract_features(black_box(&tx));
|
|
features.to_embedding()
|
|
});
|
|
});
|
|
|
|
group.finish();
|
|
}
|
|
|
|
fn bench_lsh_hashing(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("lsh_hashing");
|
|
|
|
let test_cases = vec![
|
|
("Short", "Starbucks"),
|
|
("Medium", "Amazon.com Services LLC"),
|
|
("Long", "Whole Foods Market Store #12345 Manhattan"),
|
|
("VeryLong", "Shell Gas Station #12345 - 123 Main Street, City Name, State 12345"),
|
|
];
|
|
|
|
for (name, text) in &test_cases {
|
|
group.throughput(Throughput::Bytes(text.len() as u64));
|
|
|
|
group.bench_with_input(
|
|
BenchmarkId::new("simple_lsh", name),
|
|
text,
|
|
|b, t| {
|
|
b.iter(|| {
|
|
// LSH is internal, so we extract features which calls it
|
|
let tx = Transaction {
|
|
transaction_id: "tx".to_string(),
|
|
account_id: "acc".to_string(),
|
|
amount: 50.0,
|
|
date: "2024-01-01".to_string(),
|
|
name: t.to_string(),
|
|
merchant_name: Some(t.to_string()),
|
|
category: vec!["Test".to_string()],
|
|
pending: false,
|
|
payment_channel: "online".to_string(),
|
|
};
|
|
extract_features(black_box(&tx))
|
|
});
|
|
},
|
|
);
|
|
}
|
|
|
|
group.finish();
|
|
}
|
|
|
|
fn bench_q_learning(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("q_learning");
|
|
|
|
let mut state = FinancialLearningState::default();
|
|
|
|
// Pre-populate with some Q-values
|
|
for i in 0..100 {
|
|
let key = format!("category_{}|under_budget", i % 10);
|
|
state.q_values.insert(key, 0.5 + (i as f64 * 0.01));
|
|
}
|
|
|
|
group.bench_function("update_q_value", |b| {
|
|
b.iter(|| {
|
|
update_q_value(
|
|
black_box(&state),
|
|
"Food",
|
|
"under_budget",
|
|
1.0,
|
|
0.1,
|
|
)
|
|
});
|
|
});
|
|
|
|
group.bench_function("get_recommendation", |b| {
|
|
b.iter(|| {
|
|
get_recommendation(
|
|
black_box(&state),
|
|
"Food",
|
|
500.0,
|
|
600.0,
|
|
)
|
|
});
|
|
});
|
|
|
|
group.bench_function("q_value_lookup", |b| {
|
|
b.iter(|| {
|
|
black_box(&state).q_values.get("category_5|under_budget")
|
|
});
|
|
});
|
|
|
|
group.finish();
|
|
}
|
|
|
|
// ============================================================================
|
|
// End-to-End Transaction Processing
|
|
// ============================================================================
|
|
|
|
fn bench_transaction_processing(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("transaction_processing");
|
|
|
|
// Test different batch sizes
|
|
for batch_size in [1, 10, 100, 1000] {
|
|
let transactions: Vec<Transaction> = (0..batch_size)
|
|
.map(|i| Transaction {
|
|
transaction_id: format!("tx{}", i),
|
|
account_id: "acc456".to_string(),
|
|
amount: 50.0 + (i as f64 % 100.0),
|
|
date: format!("2024-03-{:02}", (i % 28) + 1),
|
|
name: format!("Merchant {}", i % 20),
|
|
merchant_name: Some(format!("Merchant {}", i % 20)),
|
|
category: vec![
|
|
format!("Category {}", i % 5),
|
|
"Subcategory".to_string()
|
|
],
|
|
pending: false,
|
|
payment_channel: if i % 2 == 0 { "in_store" } else { "online" }.to_string(),
|
|
})
|
|
.collect();
|
|
|
|
group.throughput(Throughput::Elements(batch_size as u64));
|
|
|
|
group.bench_with_input(
|
|
BenchmarkId::new("feature_extraction_batch", batch_size),
|
|
&transactions,
|
|
|b, txs| {
|
|
b.iter(|| {
|
|
for tx in txs {
|
|
let _ = extract_features(black_box(tx));
|
|
}
|
|
});
|
|
},
|
|
);
|
|
|
|
group.bench_with_input(
|
|
BenchmarkId::new("full_pipeline_batch", batch_size),
|
|
&transactions,
|
|
|b, txs| {
|
|
b.iter(|| {
|
|
for tx in txs {
|
|
let features = extract_features(black_box(tx));
|
|
let _ = features.to_embedding();
|
|
}
|
|
});
|
|
},
|
|
);
|
|
}
|
|
|
|
group.finish();
|
|
}
|
|
|
|
// ============================================================================
|
|
// Serialization Benchmarks
|
|
// ============================================================================
|
|
|
|
fn bench_serialization(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("serialization");
|
|
|
|
// Create states with varying sizes
|
|
for tx_count in [100, 1000, 10000] {
|
|
let mut state = FinancialLearningState::default();
|
|
|
|
// Populate state to simulate real usage
|
|
for i in 0..tx_count {
|
|
let category_key = format!("category_{}", i % 10);
|
|
let pattern = SpendingPattern {
|
|
pattern_id: format!("pat_{}", i),
|
|
category: category_key.clone(),
|
|
avg_amount: 50.0 + (i as f64 % 100.0),
|
|
frequency_days: 7.0,
|
|
confidence: 0.8,
|
|
last_seen: i,
|
|
};
|
|
state.patterns.insert(category_key.clone(), pattern);
|
|
|
|
// Add Q-values
|
|
let q_key = format!("{}|under_budget", category_key);
|
|
state.q_values.insert(q_key, 0.5 + (i as f64 * 0.001));
|
|
|
|
// Add embedding (this will expose the memory leak!)
|
|
state.category_embeddings.push((
|
|
category_key,
|
|
vec![0.1 * (i as f32 % 10.0); 21]
|
|
));
|
|
}
|
|
|
|
state.version = tx_count;
|
|
|
|
let json_string = serde_json::to_string(&state).unwrap();
|
|
let state_size = json_string.len();
|
|
|
|
group.throughput(Throughput::Bytes(state_size as u64));
|
|
|
|
group.bench_with_input(
|
|
BenchmarkId::new("json_serialize", tx_count),
|
|
&state,
|
|
|b, s| {
|
|
b.iter(|| serde_json::to_string(black_box(s)).unwrap());
|
|
},
|
|
);
|
|
|
|
group.bench_with_input(
|
|
BenchmarkId::new("json_deserialize", tx_count),
|
|
&json_string,
|
|
|b, json| {
|
|
b.iter(|| {
|
|
serde_json::from_str::<FinancialLearningState>(black_box(json)).unwrap()
|
|
});
|
|
},
|
|
);
|
|
|
|
// Benchmark bincode for comparison
|
|
let bincode_data = bincode::serialize(&state).unwrap();
|
|
|
|
group.bench_with_input(
|
|
BenchmarkId::new("bincode_serialize", tx_count),
|
|
&state,
|
|
|b, s| {
|
|
b.iter(|| bincode::serialize(black_box(s)).unwrap());
|
|
},
|
|
);
|
|
|
|
group.bench_with_input(
|
|
BenchmarkId::new("bincode_deserialize", tx_count),
|
|
&bincode_data,
|
|
|b, data| {
|
|
b.iter(|| {
|
|
bincode::deserialize::<FinancialLearningState>(black_box(data)).unwrap()
|
|
});
|
|
},
|
|
);
|
|
}
|
|
|
|
group.finish();
|
|
}
|
|
|
|
// ============================================================================
|
|
// Memory Footprint Benchmarks
|
|
// ============================================================================
|
|
|
|
fn bench_memory_footprint(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("memory_footprint");
|
|
|
|
group.bench_function("proof_size_8bit", |b| {
|
|
b.iter_custom(|iters| {
|
|
let mut total_size = 0;
|
|
let start = std::time::Instant::now();
|
|
|
|
for _ in 0..iters {
|
|
let blinding = PedersenCommitment::random_blinding();
|
|
let proof = RangeProof::prove(128, 0, 255, &blinding).unwrap();
|
|
let size = bincode::serialize(&proof).unwrap().len();
|
|
total_size += size;
|
|
black_box(size);
|
|
}
|
|
|
|
println!("Average proof size (8-bit): {} bytes", total_size / iters as usize);
|
|
start.elapsed()
|
|
});
|
|
});
|
|
|
|
group.bench_function("proof_size_32bit", |b| {
|
|
b.iter_custom(|iters| {
|
|
let mut total_size = 0;
|
|
let start = std::time::Instant::now();
|
|
|
|
for _ in 0..iters {
|
|
let blinding = PedersenCommitment::random_blinding();
|
|
let proof = RangeProof::prove(50000, 0, 100000, &blinding).unwrap();
|
|
let size = bincode::serialize(&proof).unwrap().len();
|
|
total_size += size;
|
|
black_box(size);
|
|
}
|
|
|
|
println!("Average proof size (32-bit): {} bytes", total_size / iters as usize);
|
|
start.elapsed()
|
|
});
|
|
});
|
|
|
|
group.bench_function("state_growth_simulation", |b| {
|
|
b.iter_custom(|iters| {
|
|
let mut state = FinancialLearningState::default();
|
|
let start = std::time::Instant::now();
|
|
|
|
for i in 0..iters {
|
|
// Simulate transaction processing (THIS WILL LEAK MEMORY!)
|
|
let key = format!("cat_{}", i % 10);
|
|
state.category_embeddings.push((key.clone(), vec![0.0; 21]));
|
|
|
|
// Also add pattern and Q-value
|
|
let pattern = SpendingPattern {
|
|
pattern_id: format!("pat_{}", i),
|
|
category: key.clone(),
|
|
avg_amount: 50.0,
|
|
frequency_days: 7.0,
|
|
confidence: 0.8,
|
|
last_seen: i,
|
|
};
|
|
state.patterns.insert(key.clone(), pattern);
|
|
state.q_values.insert(format!("{}|action", key), 0.5);
|
|
}
|
|
|
|
let size = bincode::serialize(&state).unwrap().len();
|
|
println!("State size after {} transactions: {} KB", iters, size / 1024);
|
|
println!("Embeddings count: {}", state.category_embeddings.len());
|
|
|
|
start.elapsed()
|
|
});
|
|
});
|
|
|
|
group.finish();
|
|
}
|
|
|
|
// ============================================================================
|
|
// Regression Tests (detect performance degradation)
|
|
// ============================================================================
|
|
|
|
fn bench_regression_tests(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("regression_tests");
|
|
|
|
// These benchmarks establish baseline performance
|
|
// CI can fail if they regress significantly
|
|
|
|
group.bench_function("baseline_proof_32bit", |b| {
|
|
let blinding = PedersenCommitment::random_blinding();
|
|
b.iter(|| {
|
|
RangeProof::prove(black_box(50000), 0, black_box(100000), &blinding)
|
|
});
|
|
});
|
|
|
|
group.bench_function("baseline_feature_extraction", |b| {
|
|
let tx = Transaction {
|
|
transaction_id: "tx".to_string(),
|
|
account_id: "acc".to_string(),
|
|
amount: 50.0,
|
|
date: "2024-01-01".to_string(),
|
|
name: "Test".to_string(),
|
|
merchant_name: Some("Test Merchant".to_string()),
|
|
category: vec!["Food".to_string()],
|
|
pending: false,
|
|
payment_channel: "online".to_string(),
|
|
};
|
|
|
|
b.iter(|| {
|
|
let features = extract_features(black_box(&tx));
|
|
features.to_embedding()
|
|
});
|
|
});
|
|
|
|
group.bench_function("baseline_json_serialize_1k", |b| {
|
|
let mut state = FinancialLearningState::default();
|
|
for i in 0..1000 {
|
|
let key = format!("cat_{}", i % 10);
|
|
state.category_embeddings.push((key, vec![0.0; 21]));
|
|
}
|
|
|
|
b.iter(|| {
|
|
serde_json::to_string(black_box(&state))
|
|
});
|
|
});
|
|
|
|
group.finish();
|
|
}
|
|
|
|
// ============================================================================
|
|
// Benchmark Groups
|
|
// ============================================================================
|
|
|
|
criterion_group!(
|
|
proof_benches,
|
|
bench_proof_generation,
|
|
bench_proof_verification,
|
|
bench_pedersen_commitment,
|
|
bench_financial_proofs,
|
|
);
|
|
|
|
criterion_group!(
|
|
learning_benches,
|
|
bench_feature_extraction,
|
|
bench_lsh_hashing,
|
|
bench_q_learning,
|
|
bench_transaction_processing,
|
|
);
|
|
|
|
criterion_group!(
|
|
overhead_benches,
|
|
bench_serialization,
|
|
bench_memory_footprint,
|
|
);
|
|
|
|
criterion_group!(
|
|
regression_benches,
|
|
bench_regression_tests,
|
|
);
|
|
|
|
criterion_main!(
|
|
proof_benches,
|
|
learning_benches,
|
|
overhead_benches,
|
|
regression_benches,
|
|
);
|