reputation_core/
performance.rs

1//! Performance optimizations for the reputation engine
2//! 
3//! This module contains performance-critical optimizations including
4//! inlined functions, zero-allocation strategies, and cache-friendly
5//! data structures.
6
7use crate::Calculator;
8use reputation_types::{AgentData, ReputationScore, PriorBreakdown};
9
10/// Optimized confidence calculation with inline annotation
11#[inline(always)]
12pub fn calculate_confidence_fast(n: u32, k: f64) -> f64 {
13    let n_f64 = n as f64;
14    n_f64 / (n_f64 + k)
15}
16
17/// Optimized empirical score calculation
18#[inline(always)]
19pub fn calculate_empirical_fast(average_rating: Option<f64>) -> f64 {
20    match average_rating {
21        Some(rating) => {
22            // Convert 1-5 rating to 0-100 score
23            // (rating - 1) / 4 * 100 = (rating - 1) * 25
24            (rating - 1.0) * 25.0
25        }
26        None => 50.0, // Default for no reviews
27    }
28}
29
30/// Fast prior calculation using stack allocation
31#[inline]
32pub fn calculate_prior_fast(
33    agent: &AgentData,
34    prior_base: f64,
35    prior_max: f64,
36) -> PriorBreakdown {
37    // Stack-allocated bonus array
38    let mut bonuses = [0.0; 6];
39    let mut idx = 0;
40
41    // Base score
42    bonuses[idx] = prior_base;
43    idx += 1;
44
45    // MCP bonus (0-15 points)
46    if let Some(level) = agent.mcp_level {
47        bonuses[idx] = match level {
48            1 => 5.0,
49            2 => 10.0,
50            3 => 15.0,
51            _ => 0.0,
52        };
53        idx += 1;
54    }
55
56    // Identity verified bonus
57    if agent.identity_verified {
58        bonuses[idx] = 5.0;
59        idx += 1;
60    }
61
62    // Security audit bonus
63    if agent.security_audit_passed {
64        bonuses[idx] = 7.0;
65        idx += 1;
66    }
67
68    // Open source bonus
69    if agent.open_source {
70        bonuses[idx] = 3.0;
71        idx += 1;
72    }
73
74    // Age bonus
75    let age_days = (chrono::Utc::now() - agent.created_at).num_days();
76    if age_days > 365 {
77        bonuses[idx] = 5.0;
78    }
79
80    // Sum bonuses without allocation
81    let sum: f64 = bonuses.iter().sum();
82    let total = sum.min(prior_max);
83
84    PriorBreakdown {
85        base_score: prior_base,
86        mcp_bonus: if agent.mcp_level.is_some() { bonuses[1] } else { 0.0 },
87        identity_bonus: if agent.identity_verified { 5.0 } else { 0.0 },
88        security_audit_bonus: if agent.security_audit_passed { 7.0 } else { 0.0 },
89        open_source_bonus: if agent.open_source { 3.0 } else { 0.0 },
90        age_bonus: if age_days > 365 { 5.0 } else { 0.0 },
91        total,
92    }
93}
94
95/// Branch prediction hints for error paths
96#[cold]
97#[inline(never)]
98pub fn handle_validation_error() -> crate::error::CalculationError {
99    crate::error::CalculationError::NaNResult
100}
101
102/// Optimal chunk size for parallel processing based on CPU cache
103pub fn optimal_chunk_size() -> usize {
104    // Each AgentData is ~200 bytes, L3 cache line is typically 64 bytes
105    // Aim for chunks that fit in L3 cache (8MB typical)
106    // 8MB / 200 bytes = ~40,000 agents per chunk
107    // But we want smaller chunks for better work distribution
108    // Default to 8 CPUs if we can't detect
109    let cpus = std::thread::available_parallelism()
110        .map(|n| n.get())
111        .unwrap_or(8);
112    cpus * 64 // 64 agents per CPU for good cache locality
113}
114
115/// Cache-friendly batch calculation using optimal chunking
116pub fn calculate_batch_optimized(
117    calculator: &Calculator,
118    agents: &[AgentData],
119) -> Vec<Result<ReputationScore, crate::error::ReputationError>> {
120    use rayon::prelude::*;
121    
122    let chunk_size = optimal_chunk_size();
123    
124    agents
125        .par_chunks(chunk_size)
126        .flat_map(|chunk| {
127            chunk
128                .iter()
129                .map(|agent| calculator.calculate(agent))
130                .collect::<Vec<_>>()
131        })
132        .collect()
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138    use reputation_types::AgentDataBuilder;
139
140    #[test]
141    fn test_confidence_fast() {
142        assert_eq!(calculate_confidence_fast(0, 15.0), 0.0);
143        assert_eq!(calculate_confidence_fast(15, 15.0), 0.5);
144        assert!((calculate_confidence_fast(100, 15.0) - 0.869565).abs() < 0.00001);
145    }
146
147    #[test]
148    fn test_empirical_fast() {
149        assert_eq!(calculate_empirical_fast(Some(1.0)), 0.0);
150        assert_eq!(calculate_empirical_fast(Some(3.0)), 50.0);
151        assert_eq!(calculate_empirical_fast(Some(5.0)), 100.0);
152        assert_eq!(calculate_empirical_fast(None), 50.0);
153    }
154
155    #[test]
156    fn test_prior_fast() {
157        let agent = AgentDataBuilder::new("did:test:perf")
158            .mcp_level(2)
159            .identity_verified(true)
160            .build()
161            .unwrap();
162
163        let breakdown = calculate_prior_fast(&agent, 50.0, 80.0);
164        assert_eq!(breakdown.base_score, 50.0);
165        assert_eq!(breakdown.mcp_bonus, 10.0);
166        assert_eq!(breakdown.identity_bonus, 5.0);
167        assert_eq!(breakdown.total, 65.0);
168    }
169
170    #[test]
171    fn test_optimal_chunk_size() {
172        let size = optimal_chunk_size();
173        assert!(size > 0);
174        assert!(size <= 10000); // Reasonable upper bound
175    }
176}