Skip to main content

ipfrs_storage/
arm_profiler.rs

1//! ARM Performance Profiler
2//!
3//! Provides profiling utilities for ARM devices (Raspberry Pi, Jetson, etc.)
4//! with NEON SIMD detection and performance monitoring.
5
6use std::sync::atomic::{AtomicU64, Ordering};
7use std::sync::Arc;
8use std::time::{Duration, Instant};
9
10/// ARM architecture feature detection
11#[derive(Debug, Clone)]
12pub struct ArmFeatures {
13    /// NEON SIMD support detected
14    pub has_neon: bool,
15    /// AArch64 architecture
16    pub is_aarch64: bool,
17    /// ARMv7 architecture
18    pub is_armv7: bool,
19}
20
21impl ArmFeatures {
22    /// Detect ARM features at runtime
23    pub fn detect() -> Self {
24        let is_aarch64 = cfg!(target_arch = "aarch64");
25        let is_armv7 = cfg!(target_arch = "arm");
26
27        // NEON is standard on AArch64, optional on ARMv7
28        let has_neon = if is_aarch64 {
29            true
30        } else if is_armv7 {
31            // On ARMv7, NEON is optional - check CPU features
32            #[cfg(target_arch = "arm")]
33            {
34                // Try to detect NEON through various methods
35                // Note: This is a simplified check
36                std::arch::is_arm_feature_detected!("neon")
37            }
38            #[cfg(not(target_arch = "arm"))]
39            {
40                false
41            }
42        } else {
43            false
44        };
45
46        Self {
47            has_neon,
48            is_aarch64,
49            is_armv7,
50        }
51    }
52
53    /// Check if running on any ARM architecture
54    pub fn is_arm(&self) -> bool {
55        self.is_aarch64 || self.is_armv7
56    }
57}
58
59/// Performance counter for ARM profiling
60#[derive(Debug, Clone)]
61pub struct ArmPerfCounter {
62    name: String,
63    count: Arc<AtomicU64>,
64    total_time_ns: Arc<AtomicU64>,
65}
66
67impl ArmPerfCounter {
68    /// Create a new performance counter
69    pub fn new(name: impl Into<String>) -> Self {
70        Self {
71            name: name.into(),
72            count: Arc::new(AtomicU64::new(0)),
73            total_time_ns: Arc::new(AtomicU64::new(0)),
74        }
75    }
76
77    /// Start timing an operation
78    pub fn start(&self) -> ArmPerfTimer {
79        ArmPerfTimer {
80            counter: self.clone(),
81            start: Instant::now(),
82        }
83    }
84
85    /// Get total operation count
86    pub fn count(&self) -> u64 {
87        self.count.load(Ordering::Relaxed)
88    }
89
90    /// Get total time spent
91    pub fn total_time(&self) -> Duration {
92        Duration::from_nanos(self.total_time_ns.load(Ordering::Relaxed))
93    }
94
95    /// Get average time per operation
96    pub fn avg_time(&self) -> Duration {
97        let count = self.count();
98        Duration::from_nanos(
99            self.total_time_ns
100                .load(Ordering::Relaxed)
101                .checked_div(count)
102                .unwrap_or(0),
103        )
104    }
105
106    /// Reset counter
107    pub fn reset(&self) {
108        self.count.store(0, Ordering::Relaxed);
109        self.total_time_ns.store(0, Ordering::Relaxed);
110    }
111
112    /// Get counter name
113    pub fn name(&self) -> &str {
114        &self.name
115    }
116}
117
118/// RAII timer for performance measurement
119pub struct ArmPerfTimer {
120    counter: ArmPerfCounter,
121    start: Instant,
122}
123
124impl Drop for ArmPerfTimer {
125    fn drop(&mut self) {
126        let elapsed = self.start.elapsed().as_nanos() as u64;
127        self.counter.count.fetch_add(1, Ordering::Relaxed);
128        self.counter
129            .total_time_ns
130            .fetch_add(elapsed, Ordering::Relaxed);
131    }
132}
133
134/// ARM profiling report
135#[derive(Debug, Clone)]
136pub struct ArmPerfReport {
137    /// ARM features detected
138    pub features: ArmFeatures,
139    /// Performance counters
140    pub counters: Vec<(String, u64, Duration, Duration)>, // (name, count, total, avg)
141}
142
143impl ArmPerfReport {
144    /// Create a profiling report from counters
145    pub fn from_counters(counters: &[ArmPerfCounter]) -> Self {
146        let features = ArmFeatures::detect();
147        let counters = counters
148            .iter()
149            .map(|c| {
150                (
151                    c.name().to_string(),
152                    c.count(),
153                    c.total_time(),
154                    c.avg_time(),
155                )
156            })
157            .collect();
158
159        Self { features, counters }
160    }
161
162    /// Print report to stdout
163    pub fn print(&self) {
164        println!("=== ARM Performance Report ===");
165        println!(
166            "Architecture: {}",
167            if self.features.is_aarch64 {
168                "AArch64"
169            } else if self.features.is_armv7 {
170                "ARMv7"
171            } else {
172                "x86_64 (not ARM)"
173            }
174        );
175        println!("NEON support: {}", self.features.has_neon);
176        println!("\nPerformance Counters:");
177
178        for (name, count, total, avg) in &self.counters {
179            println!("  {name}: {count} ops, total: {total:?}, avg: {avg:?}");
180        }
181    }
182}
183
184/// ARM-optimized hash computation using NEON when available
185#[cfg(target_arch = "aarch64")]
186pub mod neon_hash {
187    use std::arch::aarch64::*;
188
189    /// Compute hash using NEON SIMD instructions (AArch64)
190    ///
191    /// This is a simplified example - real implementations would use
192    /// more sophisticated hash algorithms optimized for NEON.
193    ///
194    /// # Safety
195    ///
196    /// Caller must ensure the target CPU supports NEON (AArch64 SIMD) instructions.
197    /// This function is only valid on AArch64 targets with NEON support enabled.
198    #[target_feature(enable = "neon")]
199    pub unsafe fn hash_block_neon(data: &[u8]) -> u64 {
200        let mut hash = 0xcbf29ce484222325u64; // FNV offset basis
201        const FNV_PRIME: u64 = 0x100000001b3;
202
203        // Process 16 bytes at a time with NEON
204        let chunks = data.chunks_exact(16);
205        let remainder = chunks.remainder();
206
207        for chunk in chunks {
208            // Load 16 bytes into NEON register
209            let v = vld1q_u8(chunk.as_ptr());
210
211            // Extract bytes and update hash
212            // Note: This is a simple implementation - production code
213            // would use more efficient NEON operations
214            let bytes: [u8; 16] = std::mem::transmute(v);
215            for &byte in &bytes {
216                hash ^= byte as u64;
217                hash = hash.wrapping_mul(FNV_PRIME);
218            }
219        }
220
221        // Process remaining bytes
222        for &byte in remainder {
223            hash ^= byte as u64;
224            hash = hash.wrapping_mul(FNV_PRIME);
225        }
226
227        hash
228    }
229}
230
231/// Fallback hash computation for non-ARM or when NEON is not available
232pub fn hash_block_fallback(data: &[u8]) -> u64 {
233    let mut hash = 0xcbf29ce484222325u64; // FNV offset basis
234    const FNV_PRIME: u64 = 0x100000001b3;
235
236    for &byte in data {
237        hash ^= byte as u64;
238        hash = hash.wrapping_mul(FNV_PRIME);
239    }
240
241    hash
242}
243
244/// Hash a block using the best available method (NEON or fallback)
245pub fn hash_block(data: &[u8]) -> u64 {
246    #[cfg(target_arch = "aarch64")]
247    {
248        // Use NEON on AArch64
249        unsafe { neon_hash::hash_block_neon(data) }
250    }
251
252    #[cfg(not(target_arch = "aarch64"))]
253    {
254        // Fallback for non-ARM
255        hash_block_fallback(data)
256    }
257}
258
259/// Power profile for low-power operation tuning
260#[derive(Debug, Clone, Copy, PartialEq, Eq)]
261pub enum PowerProfile {
262    /// Maximum performance, no power saving
263    Performance,
264    /// Balanced mode with moderate batching
265    Balanced,
266    /// Low power mode with aggressive batching and delays
267    LowPower,
268    /// Custom profile with specific parameters
269    Custom {
270        batch_size: usize,
271        batch_delay_ms: u64,
272    },
273}
274
275impl PowerProfile {
276    /// Get batch size for this profile
277    pub fn batch_size(&self) -> usize {
278        match self {
279            PowerProfile::Performance => 1,
280            PowerProfile::Balanced => 10,
281            PowerProfile::LowPower => 50,
282            PowerProfile::Custom { batch_size, .. } => *batch_size,
283        }
284    }
285
286    /// Get batch delay in milliseconds
287    pub fn batch_delay_ms(&self) -> u64 {
288        match self {
289            PowerProfile::Performance => 0,
290            PowerProfile::Balanced => 10,
291            PowerProfile::LowPower => 100,
292            PowerProfile::Custom { batch_delay_ms, .. } => *batch_delay_ms,
293        }
294    }
295
296    /// Get batch delay as Duration
297    pub fn batch_delay(&self) -> Duration {
298        Duration::from_millis(self.batch_delay_ms())
299    }
300}
301
302/// Low-power operation batcher
303///
304/// Batches operations to reduce CPU wake-ups and save power.
305/// Particularly useful on battery-powered ARM devices.
306pub struct LowPowerBatcher<T> {
307    profile: PowerProfile,
308    buffer: Arc<std::sync::Mutex<Vec<T>>>,
309}
310
311impl<T> LowPowerBatcher<T> {
312    /// Create a new batcher with the given power profile
313    pub fn new(profile: PowerProfile) -> Self {
314        Self {
315            profile,
316            buffer: Arc::new(std::sync::Mutex::new(Vec::new())),
317        }
318    }
319
320    /// Add an item to the batch
321    ///
322    /// Returns the current batch if it's ready to be processed
323    pub fn push(&self, item: T) -> Option<Vec<T>> {
324        let mut buffer = self.buffer.lock().unwrap_or_else(|e| e.into_inner());
325        buffer.push(item);
326
327        if buffer.len() >= self.profile.batch_size() {
328            Some(std::mem::take(&mut *buffer))
329        } else {
330            None
331        }
332    }
333
334    /// Flush the current batch (returns all pending items)
335    pub fn flush(&self) -> Vec<T> {
336        let mut buffer = self.buffer.lock().unwrap_or_else(|e| e.into_inner());
337        std::mem::take(&mut *buffer)
338    }
339
340    /// Get the current power profile
341    pub fn profile(&self) -> PowerProfile {
342        self.profile
343    }
344
345    /// Get the number of pending items
346    pub fn pending(&self) -> usize {
347        self.buffer.lock().unwrap_or_else(|e| e.into_inner()).len()
348    }
349}
350
351/// Power statistics tracker
352#[derive(Debug, Clone, Default)]
353pub struct PowerStats {
354    /// Number of CPU wake-ups (batch flushes)
355    pub wakeups: u64,
356    /// Number of operations batched
357    pub operations: u64,
358    /// Total time spent in batched delays
359    pub delay_time: Duration,
360}
361
362impl PowerStats {
363    /// Create a new power stats tracker
364    pub fn new() -> Self {
365        Self::default()
366    }
367
368    /// Record a batch operation
369    pub fn record_batch(&mut self, ops: usize, delay: Duration) {
370        self.wakeups += 1;
371        self.operations += ops as u64;
372        self.delay_time += delay;
373    }
374
375    /// Get average operations per wake-up
376    pub fn avg_ops_per_wakeup(&self) -> f64 {
377        if self.wakeups == 0 {
378            0.0
379        } else {
380            self.operations as f64 / self.wakeups as f64
381        }
382    }
383
384    /// Get power saving ratio (higher is better)
385    ///
386    /// This estimates how much we've reduced wake-ups compared to
387    /// processing each operation individually.
388    pub fn power_saving_ratio(&self) -> f64 {
389        if self.operations == 0 {
390            1.0
391        } else {
392            self.wakeups as f64 / self.operations as f64
393        }
394    }
395}
396
397#[cfg(test)]
398mod tests {
399    use super::*;
400
401    #[test]
402    fn test_arm_features() {
403        let _features = ArmFeatures::detect();
404
405        // Should detect correctly based on compile target
406        #[cfg(target_arch = "aarch64")]
407        {
408            assert!(_features.is_aarch64);
409            assert!(_features.has_neon);
410        }
411
412        #[cfg(target_arch = "arm")]
413        {
414            assert!(_features.is_armv7);
415        }
416
417        // On non-ARM, just verify we can detect features
418        #[cfg(not(any(target_arch = "aarch64", target_arch = "arm")))]
419        {
420            assert!(!_features.is_arm());
421        }
422    }
423
424    #[test]
425    fn test_perf_counter() {
426        let counter = ArmPerfCounter::new("test_op");
427
428        {
429            let _timer = counter.start();
430            std::thread::sleep(Duration::from_millis(10));
431        }
432
433        assert_eq!(counter.count(), 1);
434        assert!(counter.total_time() >= Duration::from_millis(10));
435        assert!(counter.avg_time() >= Duration::from_millis(10));
436    }
437
438    #[test]
439    fn test_hash_block() {
440        let data = b"hello world";
441        let hash1 = hash_block(data);
442
443        // Both implementations should produce consistent results
444        #[cfg(not(target_arch = "aarch64"))]
445        {
446            let hash2 = hash_block_fallback(data);
447            assert_eq!(hash1, hash2);
448        }
449
450        // Hash should be deterministic
451        assert_eq!(hash1, hash_block(data));
452    }
453
454    #[test]
455    fn test_perf_report() {
456        let counter1 = ArmPerfCounter::new("op1");
457        let counter2 = ArmPerfCounter::new("op2");
458
459        {
460            let _t = counter1.start();
461            std::thread::sleep(Duration::from_millis(1));
462        }
463
464        {
465            let _t = counter2.start();
466            std::thread::sleep(Duration::from_millis(1));
467        }
468
469        let report = ArmPerfReport::from_counters(&[counter1, counter2]);
470        assert_eq!(report.counters.len(), 2);
471    }
472
473    #[test]
474    fn test_power_profile() {
475        let perf = PowerProfile::Performance;
476        assert_eq!(perf.batch_size(), 1);
477        assert_eq!(perf.batch_delay_ms(), 0);
478
479        let balanced = PowerProfile::Balanced;
480        assert_eq!(balanced.batch_size(), 10);
481        assert_eq!(balanced.batch_delay_ms(), 10);
482
483        let low = PowerProfile::LowPower;
484        assert_eq!(low.batch_size(), 50);
485        assert_eq!(low.batch_delay_ms(), 100);
486
487        let custom = PowerProfile::Custom {
488            batch_size: 20,
489            batch_delay_ms: 30,
490        };
491        assert_eq!(custom.batch_size(), 20);
492        assert_eq!(custom.batch_delay_ms(), 30);
493    }
494
495    #[test]
496    fn test_low_power_batcher() {
497        let batcher: LowPowerBatcher<i32> = LowPowerBatcher::new(PowerProfile::Custom {
498            batch_size: 3,
499            batch_delay_ms: 0,
500        });
501
502        assert_eq!(batcher.pending(), 0);
503
504        // First two pushes shouldn't trigger batch
505        assert!(batcher.push(1).is_none());
506        assert_eq!(batcher.pending(), 1);
507
508        assert!(batcher.push(2).is_none());
509        assert_eq!(batcher.pending(), 2);
510
511        // Third push should trigger batch
512        let batch = batcher.push(3);
513        assert!(batch.is_some());
514        let batch = batch.unwrap();
515        assert_eq!(batch, vec![1, 2, 3]);
516        assert_eq!(batcher.pending(), 0);
517
518        // Test flush
519        batcher.push(4);
520        batcher.push(5);
521        let flushed = batcher.flush();
522        assert_eq!(flushed, vec![4, 5]);
523        assert_eq!(batcher.pending(), 0);
524    }
525
526    #[test]
527    fn test_power_stats() {
528        let mut stats = PowerStats::new();
529        assert_eq!(stats.wakeups, 0);
530        assert_eq!(stats.operations, 0);
531
532        stats.record_batch(10, Duration::from_millis(5));
533        assert_eq!(stats.wakeups, 1);
534        assert_eq!(stats.operations, 10);
535        assert_eq!(stats.avg_ops_per_wakeup(), 10.0);
536
537        stats.record_batch(5, Duration::from_millis(5));
538        assert_eq!(stats.wakeups, 2);
539        assert_eq!(stats.operations, 15);
540        assert_eq!(stats.avg_ops_per_wakeup(), 7.5);
541
542        // Power saving ratio: 2 wakeups / 15 operations ≈ 0.133
543        let ratio = stats.power_saving_ratio();
544        assert!(ratio > 0.0 && ratio < 1.0);
545    }
546}