File size: 5,260 Bytes
26cf661
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
//! Benchmark: Rust graph build + predict vs equivalent Python workload.
//!
//! Run with: cargo test --release bench_ -- --nocapture

#[cfg(test)]
mod bench {
    use crate::graph::AccessGraph;
    use crate::predictor::RustPredictor;
    use std::time::Instant;

    /// Generate a realistic workload: N layers, some hot, some cold,
    /// with causal chains. Same pattern as test_predictor.py.
    fn generate_inference_workload(
        num_layers: u32,
        num_hot: u32,
        iterations: u32,
    ) -> Vec<(u64, String, u64)> {
        let mut events = Vec::new();
        let mut ts: u64 = 0;

        for _ in 0..iterations {
            // Hot layers accessed every iteration
            for i in 0..num_hot {
                events.push((ts, format!("layer_{}", i), 65536));
                ts += 100_000; // 0.1ms between accesses
            }

            // Cold layers: 3% chance
            for i in num_hot..num_layers {
                if (ts / 1000 + i as u64) % 33 == 0 {
                    events.push((ts, format!("layer_{}", i), 65536));
                    ts += 100_000;
                }
            }

            ts += 2_000_000; // 2ms between iterations
        }

        events
    }

    #[test]
    fn bench_graph_build_small() {
        // 16 layers, 4 hot, 100 iterations — ~similar to Python test
        let events = generate_inference_workload(16, 4, 100);
        println!("\nSmall workload: {} events", events.len());

        let start = Instant::now();
        let mut graph = AccessGraph::new(5_000_000, 0.7);
        graph.build(events.clone());
        let elapsed = start.elapsed();

        println!("  Graph build: {:?}", elapsed);
        println!("  Nodes: {}, Edges: {}, Clusters: {}",
                 graph.node_count(), graph.edge_count(), graph.cluster_count());

        // Predict
        let mut predictor = RustPredictor::new();
        predictor.learn(&graph);

        let start = Instant::now();
        let result = predictor.score(events);
        let elapsed = start.elapsed();

        println!("  Score: {:?}", elapsed);
        println!("  Accuracy: {}%, Predictions: {}, Hits: {}",
                 result.accuracy, result.predictions_made, result.hits);
    }

    #[test]
    fn bench_graph_build_medium() {
        // 64 layers, 8 hot, 100 iterations
        let events = generate_inference_workload(64, 8, 100);
        println!("\nMedium workload: {} events", events.len());

        let start = Instant::now();
        let mut graph = AccessGraph::new(5_000_000, 0.7);
        graph.build(events.clone());
        let elapsed = start.elapsed();

        println!("  Graph build: {:?}", elapsed);
        println!("  Nodes: {}, Edges: {}, Clusters: {}",
                 graph.node_count(), graph.edge_count(), graph.cluster_count());

        let mut predictor = RustPredictor::new();
        predictor.learn(&graph);

        let start = Instant::now();
        let result = predictor.score(events);
        let elapsed = start.elapsed();

        println!("  Score: {:?}", elapsed);
        println!("  Accuracy: {}%, Predictions: {}, Hits: {}",
                 result.accuracy, result.predictions_made, result.hits);
    }

    #[test]
    fn bench_graph_build_large() {
        // 256 layers, 16 hot, 50 iterations — stress test
        let events = generate_inference_workload(256, 16, 50);
        println!("\nLarge workload: {} events", events.len());

        let start = Instant::now();
        let mut graph = AccessGraph::new(5_000_000, 0.7);
        graph.build(events.clone());
        let elapsed = start.elapsed();

        println!("  Graph build: {:?}", elapsed);
        println!("  Nodes: {}, Edges: {}, Clusters: {}",
                 graph.node_count(), graph.edge_count(), graph.cluster_count());

        let mut predictor = RustPredictor::new();
        predictor.learn(&graph);

        let start = Instant::now();
        let result = predictor.score(events);
        let elapsed = start.elapsed();

        println!("  Score: {:?}", elapsed);
        println!("  Accuracy: {}%, Predictions: {}, Hits: {}",
                 result.accuracy, result.predictions_made, result.hits);
    }

    #[test]
    fn bench_predict_latency() {
        // Measure single-prediction latency — this is the hot path
        let events = generate_inference_workload(64, 8, 100);

        let mut graph = AccessGraph::new(5_000_000, 0.7);
        graph.build(events);

        let mut predictor = RustPredictor::new();
        predictor.learn(&graph);

        // Warm up
        for _ in 0..100 {
            let _ = predictor.predict("layer_0", 10);
        }

        // Measure
        let iterations = 100_000;
        let start = Instant::now();
        for _ in 0..iterations {
            let _ = predictor.predict("layer_0", 10);
        }
        let elapsed = start.elapsed();

        let per_predict_ns = elapsed.as_nanos() / iterations as u128;
        println!("\nSingle predict() latency:");
        println!("  {} iterations in {:?}", iterations, elapsed);
        println!("  *** {per_predict_ns} ns per prediction ***");
        println!("  ({:.1} million predictions/sec)",
                 1_000_000_000.0 / per_predict_ns as f64);
    }
}