mathhook_core/core/performance/
profiler.rs1use std::collections::HashMap;
7use std::sync::{Arc, OnceLock, RwLock};
8use std::time::{Duration, Instant};
9
10#[derive(Debug, Clone)]
12pub struct PerformanceMeasurement {
13 pub operation_type: String,
15 pub operation_size: usize,
17 pub duration: Duration,
19 pub timestamp: Instant,
21}
22
23#[derive(Debug, Clone)]
25pub struct AdaptiveThresholds {
26 pub simd_threshold: usize,
28 pub parallel_threshold: usize,
30 pub confidence: f64,
32 pub sample_count: usize,
34}
35
36impl Default for AdaptiveThresholds {
37 fn default() -> Self {
38 Self {
39 simd_threshold: 50, parallel_threshold: 1000, confidence: 0.0, sample_count: 0,
43 }
44 }
45}
46
47pub struct RuntimeProfiler {
49 measurements: Arc<RwLock<Vec<PerformanceMeasurement>>>,
51 thresholds: Arc<RwLock<AdaptiveThresholds>>,
53 max_measurements: usize,
55 min_samples_for_adaptation: usize,
57}
58
59impl Default for RuntimeProfiler {
60 fn default() -> Self {
61 Self::new()
62 }
63}
64
65impl RuntimeProfiler {
66 pub fn new() -> Self {
68 Self {
69 measurements: Arc::new(RwLock::new(Vec::new())),
70 thresholds: Arc::new(RwLock::new(AdaptiveThresholds::default())),
71 max_measurements: 10000, min_samples_for_adaptation: 50, }
74 }
75
76 pub fn record_measurement(&self, measurement: PerformanceMeasurement) {
78 if let Ok(mut measurements) = self.measurements.write() {
79 measurements.push(measurement);
80
81 if measurements.len() > self.max_measurements {
83 let len = measurements.len();
84 measurements.drain(0..len - self.max_measurements);
85 }
86
87 if measurements.len() >= self.min_samples_for_adaptation {
89 self.adapt_thresholds(&measurements);
90 }
91 }
92 }
93
94 pub fn get_thresholds(&self) -> AdaptiveThresholds {
96 self.thresholds
97 .read()
98 .unwrap_or_else(|poisoned| poisoned.into_inner())
99 .clone()
100 }
101
102 fn adapt_thresholds(&self, measurements: &[PerformanceMeasurement]) {
104 let simd_threshold = self.find_optimal_simd_threshold(measurements);
105 let parallel_threshold = self.find_optimal_parallel_threshold(measurements);
106
107 if let Ok(mut thresholds) = self.thresholds.write() {
108 let old_simd = thresholds.simd_threshold;
109 let old_parallel = thresholds.parallel_threshold;
110
111 let alpha = 0.1; thresholds.simd_threshold =
114 ((1.0 - alpha) * old_simd as f64 + alpha * simd_threshold as f64) as usize;
115 thresholds.parallel_threshold =
116 ((1.0 - alpha) * old_parallel as f64 + alpha * parallel_threshold as f64) as usize;
117
118 thresholds.sample_count = measurements.len();
120 thresholds.confidence = self.calculate_confidence(measurements);
121
122 if old_simd != thresholds.simd_threshold
124 || old_parallel != thresholds.parallel_threshold
125 {
126 println!("Adaptive thresholds updated: SIMD {} -> {}, Parallel {} -> {} (confidence: {:.2})",
127 old_simd, thresholds.simd_threshold,
128 old_parallel, thresholds.parallel_threshold,
129 thresholds.confidence
130 );
131 }
132 }
133 }
134
135 fn find_optimal_simd_threshold(&self, measurements: &[PerformanceMeasurement]) -> usize {
137 let mut simd_measurements: Vec<_> = measurements
138 .iter()
139 .filter(|m| m.operation_type.contains("simd"))
140 .collect();
141 let mut sequential_measurements: Vec<_> = measurements
142 .iter()
143 .filter(|m| m.operation_type.contains("sequential"))
144 .collect();
145
146 if simd_measurements.is_empty() || sequential_measurements.is_empty() {
147 return 50; }
149
150 simd_measurements.sort_by_key(|m| m.operation_size);
152 sequential_measurements.sort_by_key(|m| m.operation_size);
153
154 for size in (10..=1000).step_by(10) {
156 let simd_perf = self.estimate_performance_at_size(&simd_measurements, size);
157 let seq_perf = self.estimate_performance_at_size(&sequential_measurements, size);
158
159 if let (Some(simd_time), Some(seq_time)) = (simd_perf, seq_perf) {
160 if simd_time < seq_time {
161 return size;
162 }
163 }
164 }
165
166 50 }
168
169 fn find_optimal_parallel_threshold(&self, measurements: &[PerformanceMeasurement]) -> usize {
171 let mut parallel_measurements: Vec<_> = measurements
172 .iter()
173 .filter(|m| m.operation_type.contains("parallel"))
174 .collect();
175 let mut sequential_measurements: Vec<_> = measurements
176 .iter()
177 .filter(|m| m.operation_type.contains("sequential"))
178 .collect();
179
180 if parallel_measurements.is_empty() || sequential_measurements.is_empty() {
181 return 1000; }
183
184 parallel_measurements.sort_by_key(|m| m.operation_size);
186 sequential_measurements.sort_by_key(|m| m.operation_size);
187
188 for size in (100..=5000).step_by(100) {
190 let parallel_perf = self.estimate_performance_at_size(¶llel_measurements, size);
191 let seq_perf = self.estimate_performance_at_size(&sequential_measurements, size);
192
193 if let (Some(parallel_time), Some(seq_time)) = (parallel_perf, seq_perf) {
194 if parallel_time < seq_time {
195 return size;
196 }
197 }
198 }
199
200 1000 }
202
203 fn estimate_performance_at_size(
205 &self,
206 measurements: &[&PerformanceMeasurement],
207 target_size: usize,
208 ) -> Option<Duration> {
209 if measurements.is_empty() {
210 return None;
211 }
212
213 let mut closest_smaller = None;
215 let mut closest_larger = None;
216
217 for measurement in measurements {
218 if measurement.operation_size <= target_size {
219 closest_smaller = Some(measurement);
220 } else if closest_larger.is_none() {
221 closest_larger = Some(measurement);
222 break;
223 }
224 }
225
226 match (closest_smaller, closest_larger) {
227 (Some(smaller), Some(larger)) => {
228 let size_diff = larger.operation_size - smaller.operation_size;
230 let time_diff =
231 larger.duration.as_nanos() as f64 - smaller.duration.as_nanos() as f64;
232 let target_offset = target_size - smaller.operation_size;
233
234 let interpolated_nanos = smaller.duration.as_nanos() as f64
235 + (time_diff * target_offset as f64) / size_diff as f64;
236
237 Some(Duration::from_nanos(interpolated_nanos as u64))
238 }
239 (Some(measurement), None) | (None, Some(measurement)) => {
240 Some(measurement.duration)
242 }
243 (None, None) => None,
244 }
245 }
246
247 fn calculate_confidence(&self, measurements: &[PerformanceMeasurement]) -> f64 {
249 if measurements.len() < 10 {
250 return 0.0;
251 }
252
253 let recent_measurements: Vec<_> = measurements
255 .iter()
256 .rev()
257 .take(100) .collect();
259
260 if recent_measurements.is_empty() {
261 return 0.0;
262 }
263
264 let mut type_groups: HashMap<String, Vec<Duration>> = HashMap::new();
266 for measurement in recent_measurements {
267 type_groups
268 .entry(measurement.operation_type.clone())
269 .or_default()
270 .push(measurement.duration);
271 }
272
273 let mut total_consistency = 0.0;
274 let mut group_count = 0;
275
276 for (_, durations) in type_groups {
277 if durations.len() < 3 {
278 continue;
279 }
280
281 let mean_duration =
282 durations.iter().sum::<Duration>().as_nanos() as f64 / durations.len() as f64;
283 let variance = durations
284 .iter()
285 .map(|d| {
286 let diff = d.as_nanos() as f64 - mean_duration;
287 diff * diff
288 })
289 .sum::<f64>()
290 / durations.len() as f64;
291
292 let coefficient_of_variation = if mean_duration > 0.0 {
293 variance.sqrt() / mean_duration
294 } else {
295 1.0
296 };
297
298 let consistency = 1.0 / (1.0 + coefficient_of_variation);
300 total_consistency += consistency;
301 group_count += 1;
302 }
303
304 if group_count > 0 {
305 (total_consistency / group_count as f64).min(1.0)
306 } else {
307 0.0
308 }
309 }
310
311 pub fn get_statistics(&self) -> ProfilerStatistics {
313 let measurements = self
314 .measurements
315 .read()
316 .expect("BUG: Profiler measurements lock poisoned - indicates panic during profiler read in another thread");
317 let thresholds = self.get_thresholds();
318
319 let total_measurements = measurements.len();
320 let recent_measurements = measurements.iter().rev().take(100).count();
321
322 let mut type_stats: HashMap<String, (Duration, usize)> = HashMap::new();
324 for measurement in measurements.iter().rev().take(1000) {
325 let (total_duration, count) = type_stats
326 .entry(measurement.operation_type.clone())
327 .or_insert((Duration::ZERO, 0));
328 *total_duration += measurement.duration;
329 *count += 1;
330 }
331
332 let average_performance: HashMap<String, Duration> = type_stats
333 .into_iter()
334 .map(|(op_type, (total_duration, count))| {
335 let avg_duration = if count > 0 {
336 Duration::from_nanos((total_duration.as_nanos() / count as u128) as u64)
337 } else {
338 Duration::ZERO
339 };
340 (op_type, avg_duration)
341 })
342 .collect();
343
344 ProfilerStatistics {
345 total_measurements,
346 recent_measurements,
347 current_thresholds: thresholds,
348 average_performance,
349 }
350 }
351}
352
353#[derive(Debug, Clone)]
355pub struct ProfilerStatistics {
356 pub total_measurements: usize,
358 pub recent_measurements: usize,
360 pub current_thresholds: AdaptiveThresholds,
362 pub average_performance: HashMap<String, Duration>,
364}
365
366static GLOBAL_PROFILER: OnceLock<RuntimeProfiler> = OnceLock::new();
368
369pub fn get_global_profiler() -> &'static RuntimeProfiler {
371 GLOBAL_PROFILER.get_or_init(RuntimeProfiler::new)
372}
373
374pub fn record_performance(operation_type: &str, operation_size: usize, duration: Duration) {
376 let measurement = PerformanceMeasurement {
377 operation_type: operation_type.to_owned(),
378 operation_size,
379 duration,
380 timestamp: Instant::now(),
381 };
382
383 get_global_profiler().record_measurement(measurement);
384}
385
386pub fn get_adaptive_thresholds() -> AdaptiveThresholds {
388 get_global_profiler().get_thresholds()
389}
390
391pub fn get_profiler_statistics() -> ProfilerStatistics {
393 get_global_profiler().get_statistics()
394}
395
396#[cfg(test)]
397mod tests {
398 use super::*;
399
400 #[test]
401 fn test_runtime_profiler_creation() {
402 let profiler = RuntimeProfiler::new();
403 let thresholds = profiler.get_thresholds();
404
405 assert_eq!(thresholds.simd_threshold, 50);
406 assert_eq!(thresholds.parallel_threshold, 1000);
407 assert_eq!(thresholds.confidence, 0.0);
408 assert_eq!(thresholds.sample_count, 0);
409 }
410
411 #[test]
412 fn test_measurement_recording() {
413 let profiler = RuntimeProfiler::new();
414
415 let measurement = PerformanceMeasurement {
416 operation_type: "test_operation".to_string(),
417 operation_size: 100,
418 duration: Duration::from_millis(10),
419 timestamp: Instant::now(),
420 };
421
422 profiler.record_measurement(measurement);
423
424 let thresholds = profiler.get_thresholds();
426 assert_eq!(thresholds.sample_count, 0); }
428
429 #[test]
430 fn test_global_profiler() {
431 record_performance("test_simd", 100, Duration::from_micros(50));
432 record_performance("test_sequential", 100, Duration::from_micros(100));
433
434 let stats = get_profiler_statistics();
435 assert!(stats.total_measurements >= 2);
436
437 let thresholds = get_adaptive_thresholds();
438 assert!(thresholds.simd_threshold > 0);
439 assert!(thresholds.parallel_threshold > 0);
440 }
441}