Skip to main content

torsh_ffi/
performance.rs

1//! Performance optimizations and batched operations for ToRSh FFI
2//!
3//! This module provides optimized operations for better performance in FFI scenarios,
4//! including batched operations, memory pooling, and async processing.
5
6#![allow(dead_code)]
7
8use crate::c_api::*;
9use crate::error::{FfiError, FfiResult};
10use parking_lot::{Mutex, RwLock};
11use std::collections::VecDeque;
12use std::os::raw::{c_char, c_float, c_int};
13use std::ptr;
14use std::sync::atomic::{AtomicU64, Ordering};
15use std::sync::Arc;
16use std::time::Instant;
17
18/// Performance statistics for monitoring FFI operations
19/// Uses atomic operations for lock-free counter updates
20#[derive(Debug)]
21pub struct PerformanceStats {
22    pub total_operations: AtomicU64,
23    pub total_time_ms: AtomicU64,
24    pub min_time_ms: AtomicU64,
25    pub max_time_ms: AtomicU64,
26    pub cache_hits: AtomicU64,
27    pub cache_misses: AtomicU64,
28    pub memory_pool_allocations: AtomicU64,
29    pub memory_pool_deallocations: AtomicU64,
30}
31
32impl Default for PerformanceStats {
33    fn default() -> Self {
34        Self::new()
35    }
36}
37
38impl PerformanceStats {
39    pub fn new() -> Self {
40        Self {
41            total_operations: AtomicU64::new(0),
42            total_time_ms: AtomicU64::new(0),
43            min_time_ms: AtomicU64::new(u64::MAX),
44            max_time_ms: AtomicU64::new(0),
45            cache_hits: AtomicU64::new(0),
46            cache_misses: AtomicU64::new(0),
47            memory_pool_allocations: AtomicU64::new(0),
48            memory_pool_deallocations: AtomicU64::new(0),
49        }
50    }
51
52    /// Record an operation with lock-free atomic updates
53    pub fn record_operation(&self, duration_ms: u64) {
54        self.total_operations.fetch_add(1, Ordering::Relaxed);
55        self.total_time_ms.fetch_add(duration_ms, Ordering::Relaxed);
56
57        // Update min using compare-and-swap loop
58        let mut current_min = self.min_time_ms.load(Ordering::Relaxed);
59        while duration_ms < current_min {
60            match self.min_time_ms.compare_exchange_weak(
61                current_min,
62                duration_ms,
63                Ordering::Relaxed,
64                Ordering::Relaxed,
65            ) {
66                Ok(_) => break,
67                Err(actual) => current_min = actual,
68            }
69        }
70
71        // Update max using compare-and-swap loop
72        let mut current_max = self.max_time_ms.load(Ordering::Relaxed);
73        while duration_ms > current_max {
74            match self.max_time_ms.compare_exchange_weak(
75                current_max,
76                duration_ms,
77                Ordering::Relaxed,
78                Ordering::Relaxed,
79            ) {
80                Ok(_) => break,
81                Err(actual) => current_max = actual,
82            }
83        }
84    }
85
86    pub fn record_cache_hit(&self) {
87        self.cache_hits.fetch_add(1, Ordering::Relaxed);
88    }
89
90    pub fn record_cache_miss(&self) {
91        self.cache_misses.fetch_add(1, Ordering::Relaxed);
92    }
93
94    pub fn record_allocation(&self) {
95        self.memory_pool_allocations.fetch_add(1, Ordering::Relaxed);
96    }
97
98    pub fn record_deallocation(&self) {
99        self.memory_pool_deallocations
100            .fetch_add(1, Ordering::Relaxed);
101    }
102
103    pub fn avg_time_ms(&self) -> f64 {
104        let total_ops = self.total_operations.load(Ordering::Relaxed);
105        if total_ops == 0 {
106            0.0
107        } else {
108            self.total_time_ms.load(Ordering::Relaxed) as f64 / total_ops as f64
109        }
110    }
111
112    pub fn cache_hit_rate(&self) -> f64 {
113        let hits = self.cache_hits.load(Ordering::Relaxed);
114        let misses = self.cache_misses.load(Ordering::Relaxed);
115        if hits + misses == 0 {
116            0.0
117        } else {
118            hits as f64 / (hits + misses) as f64
119        }
120    }
121
122    /// Get a snapshot of stats for display/reporting
123    pub fn snapshot(&self) -> PerformanceStatsSnapshot {
124        PerformanceStatsSnapshot {
125            total_operations: self.total_operations.load(Ordering::Relaxed),
126            total_time_ms: self.total_time_ms.load(Ordering::Relaxed),
127            avg_time_ms: self.avg_time_ms(),
128            min_time_ms: self.min_time_ms.load(Ordering::Relaxed),
129            max_time_ms: self.max_time_ms.load(Ordering::Relaxed),
130            cache_hits: self.cache_hits.load(Ordering::Relaxed),
131            cache_misses: self.cache_misses.load(Ordering::Relaxed),
132            memory_pool_allocations: self.memory_pool_allocations.load(Ordering::Relaxed),
133            memory_pool_deallocations: self.memory_pool_deallocations.load(Ordering::Relaxed),
134        }
135    }
136
137    /// Reset all stats to zero
138    pub fn reset(&self) {
139        self.total_operations.store(0, Ordering::Relaxed);
140        self.total_time_ms.store(0, Ordering::Relaxed);
141        self.min_time_ms.store(u64::MAX, Ordering::Relaxed);
142        self.max_time_ms.store(0, Ordering::Relaxed);
143        self.cache_hits.store(0, Ordering::Relaxed);
144        self.cache_misses.store(0, Ordering::Relaxed);
145        self.memory_pool_allocations.store(0, Ordering::Relaxed);
146        self.memory_pool_deallocations.store(0, Ordering::Relaxed);
147    }
148}
149
150/// Snapshot of performance statistics (for display/reporting)
151#[derive(Debug, Clone, Default)]
152pub struct PerformanceStatsSnapshot {
153    pub total_operations: u64,
154    pub total_time_ms: u64,
155    pub avg_time_ms: f64,
156    pub min_time_ms: u64,
157    pub max_time_ms: u64,
158    pub cache_hits: u64,
159    pub cache_misses: u64,
160    pub memory_pool_allocations: u64,
161    pub memory_pool_deallocations: u64,
162}
163
164impl PerformanceStatsSnapshot {
165    pub fn cache_hit_rate(&self) -> f64 {
166        if self.cache_hits + self.cache_misses == 0 {
167            0.0
168        } else {
169            self.cache_hits as f64 / (self.cache_hits + self.cache_misses) as f64
170        }
171    }
172}
173
174/// Global performance statistics (lock-free with atomics)
175static PERF_STATS: std::sync::LazyLock<Arc<PerformanceStats>> =
176    std::sync::LazyLock::new(|| Arc::new(PerformanceStats::new()));
177
178/// Operation cache for frequently used operations
179#[derive(Debug)]
180pub struct OperationCache {
181    cache: RwLock<std::collections::HashMap<String, CachedOperation>>,
182    max_size: usize,
183    ttl_ms: u64,
184}
185
186#[derive(Debug, Clone)]
187struct CachedOperation {
188    result: Vec<u8>, // Serialized result
189    created_at: Instant,
190    access_count: u64,
191}
192
193impl OperationCache {
194    pub fn new(max_size: usize, ttl_ms: u64) -> Self {
195        Self {
196            cache: RwLock::new(std::collections::HashMap::new()),
197            max_size,
198            ttl_ms,
199        }
200    }
201
202    fn get(&self, key: &str) -> Option<Vec<u8>> {
203        let cache = self.cache.read();
204        if let Some(cached) = cache.get(key) {
205            if cached.created_at.elapsed().as_millis() as u64 <= self.ttl_ms {
206                PERF_STATS.record_cache_hit();
207                return Some(cached.result.clone());
208            }
209        }
210
211        PERF_STATS.record_cache_miss();
212        None
213    }
214
215    fn put(&self, key: String, value: Vec<u8>) {
216        let mut cache = self.cache.write();
217
218        // Remove expired entries and maintain size limit
219        if cache.len() >= self.max_size {
220            let now = Instant::now();
221            cache.retain(|_, v| now.duration_since(v.created_at).as_millis() as u64 <= self.ttl_ms);
222
223            // If still at capacity, remove oldest entries
224            if cache.len() >= self.max_size {
225                let mut entries: Vec<_> = cache.iter().collect();
226                entries.sort_by_key(|(_, v)| v.created_at);
227
228                // Collect keys to remove first
229                let keys_to_remove: Vec<_> = entries
230                    .iter()
231                    .take(cache.len() - self.max_size + 1)
232                    .map(|(key, _)| (*key).clone())
233                    .collect();
234
235                // Now remove them
236                for key in keys_to_remove {
237                    cache.remove(&key);
238                }
239            }
240        }
241
242        cache.insert(
243            key,
244            CachedOperation {
245                result: value,
246                created_at: Instant::now(),
247                access_count: 1,
248            },
249        );
250    }
251}
252
253/// Global operation cache
254static OP_CACHE: std::sync::LazyLock<OperationCache> =
255    std::sync::LazyLock::new(|| OperationCache::new(1000, 300000)); // 1000 items, 5 min TTL
256
257/// Memory pool for efficient tensor memory management
258#[derive(Debug)]
259pub struct TensorMemoryPool {
260    pools: RwLock<std::collections::HashMap<usize, Vec<Vec<f32>>>>,
261    max_pool_size: usize,
262    max_buffer_size: usize,
263}
264
265impl TensorMemoryPool {
266    fn new(max_pool_size: usize, max_buffer_size: usize) -> Self {
267        Self {
268            pools: RwLock::new(std::collections::HashMap::new()),
269            max_pool_size,
270            max_buffer_size,
271        }
272    }
273
274    /// Get a pre-allocated buffer from the pool or create a new one
275    pub fn get_buffer(&self, size: usize) -> Vec<f32> {
276        if size > self.max_buffer_size {
277            // For very large buffers, don't use pooling
278            return Vec::with_capacity(size);
279        }
280
281        let mut pools = self.pools.write();
282        if let Some(pool) = pools.get_mut(&size) {
283            if let Some(mut buffer) = pool.pop() {
284                buffer.clear();
285                buffer.reserve(size);
286
287                PERF_STATS.record_allocation();
288
289                return buffer;
290            }
291        }
292
293        // No available buffer in pool, create new one
294        Vec::with_capacity(size)
295    }
296
297    /// Return a buffer to the pool for reuse
298    pub fn return_buffer(&self, mut buffer: Vec<f32>) {
299        let size = buffer.capacity();
300        if size > self.max_buffer_size {
301            return; // Don't pool very large buffers
302        }
303
304        buffer.clear();
305
306        let mut pools = self.pools.write();
307        let pool = pools.entry(size).or_insert_with(Vec::new);
308
309        if pool.len() < self.max_pool_size {
310            pool.push(buffer);
311
312            PERF_STATS.record_deallocation();
313        }
314    }
315
316    /// Get pool statistics
317    pub fn stats(&self) -> PoolStats {
318        let pools = self.pools.read();
319        let mut total_pools = 0;
320        let mut total_buffers = 0;
321        let mut memory_usage = 0;
322
323        for (size, pool) in pools.iter() {
324            total_pools += 1;
325            total_buffers += pool.len();
326            memory_usage += size * pool.len() * std::mem::size_of::<f32>();
327        }
328
329        PoolStats {
330            total_pools,
331            total_buffers,
332            memory_usage,
333        }
334    }
335
336    /// Clean up expired or unused buffers
337    pub fn cleanup(&self) {
338        let mut pools = self.pools.write();
339        // Keep only smaller pools and limit buffer count per pool
340        pools.retain(|size, pool| {
341            if *size > self.max_buffer_size / 4 {
342                pool.truncate(self.max_pool_size / 4);
343            }
344            !pool.is_empty()
345        });
346    }
347}
348
349#[derive(Debug, Clone)]
350pub struct PoolStats {
351    pub total_pools: usize,
352    pub total_buffers: usize,
353    pub memory_usage: usize,
354}
355
356/// Global memory pool
357static MEMORY_POOL: std::sync::LazyLock<TensorMemoryPool> =
358    std::sync::LazyLock::new(|| TensorMemoryPool::new(50, 1_000_000)); // 50 buffers per size, max 1M elements
359
360/// Batched tensor operations for better performance
361#[derive(Debug)]
362pub struct BatchedOperations {
363    tensors: Vec<*mut TorshTensor>,
364    operations: Vec<BatchOperation>,
365}
366
367#[derive(Debug, Clone)]
368pub enum BatchOperation {
369    Add { a_idx: usize, b_idx: usize },
370    Mul { a_idx: usize, b_idx: usize },
371    MatMul { a_idx: usize, b_idx: usize },
372    ReLU { tensor_idx: usize },
373    ScalarAdd { tensor_idx: usize, scalar: f32 },
374    ScalarMul { tensor_idx: usize, scalar: f32 },
375}
376
377impl BatchedOperations {
378    pub fn new() -> Self {
379        Self {
380            tensors: Vec::new(),
381            operations: Vec::new(),
382        }
383    }
384
385    pub fn add_tensor(&mut self, tensor: *mut TorshTensor) -> usize {
386        let idx = self.tensors.len();
387        self.tensors.push(tensor);
388        idx
389    }
390
391    pub fn add_operation(&mut self, op: BatchOperation) {
392        self.operations.push(op);
393    }
394
395    pub fn execute_batch(&self) -> FfiResult<Vec<*mut TorshTensor>> {
396        let start = Instant::now();
397        let mut results = Vec::new();
398
399        for operation in &self.operations {
400            let result = match operation {
401                BatchOperation::Add { a_idx, b_idx } => unsafe {
402                    // Create output tensor with same shape as first input
403                    let ndim = torsh_tensor_ndim(self.tensors[*a_idx]);
404                    if ndim == 0 {
405                        return Err(FfiError::Tensor {
406                            message: "Invalid tensor dimensions".to_string(),
407                        });
408                    }
409                    let mut shape_vec = vec![0usize; ndim];
410                    let mut ndim_out = ndim;
411                    torsh_tensor_shape(self.tensors[*a_idx], shape_vec.as_mut_ptr(), &mut ndim_out);
412                    let output_tensor = torsh_tensor_zeros(shape_vec.as_ptr(), ndim);
413                    if output_tensor.is_null() {
414                        ptr::null_mut()
415                    } else {
416                        let error = torsh_tensor_add(
417                            self.tensors[*a_idx],
418                            self.tensors[*b_idx],
419                            output_tensor,
420                        );
421                        if error != TorshError::Success {
422                            ptr::null_mut()
423                        } else {
424                            output_tensor
425                        }
426                    }
427                },
428                BatchOperation::Mul { a_idx, b_idx } => unsafe {
429                    // Create output tensor with same shape as first input
430                    let ndim = torsh_tensor_ndim(self.tensors[*a_idx]);
431                    if ndim == 0 {
432                        return Err(FfiError::Tensor {
433                            message: "Invalid tensor dimensions".to_string(),
434                        });
435                    }
436                    let mut shape_vec = vec![0usize; ndim];
437                    let mut ndim_out = ndim;
438                    torsh_tensor_shape(self.tensors[*a_idx], shape_vec.as_mut_ptr(), &mut ndim_out);
439                    let output_tensor = torsh_tensor_zeros(shape_vec.as_ptr(), ndim);
440                    if output_tensor.is_null() {
441                        ptr::null_mut()
442                    } else {
443                        let error = torsh_tensor_mul(
444                            self.tensors[*a_idx],
445                            self.tensors[*b_idx],
446                            output_tensor,
447                        );
448                        if error != TorshError::Success {
449                            ptr::null_mut()
450                        } else {
451                            output_tensor
452                        }
453                    }
454                },
455                BatchOperation::MatMul { a_idx, b_idx } => unsafe {
456                    // Create output tensor with same shape as first input
457                    let ndim = torsh_tensor_ndim(self.tensors[*a_idx]);
458                    if ndim == 0 {
459                        return Err(FfiError::Tensor {
460                            message: "Invalid tensor dimensions".to_string(),
461                        });
462                    }
463                    let mut shape_vec = vec![0usize; ndim];
464                    let mut ndim_out = ndim;
465                    torsh_tensor_shape(self.tensors[*a_idx], shape_vec.as_mut_ptr(), &mut ndim_out);
466                    let output_tensor = torsh_tensor_zeros(shape_vec.as_ptr(), ndim);
467                    if output_tensor.is_null() {
468                        ptr::null_mut()
469                    } else {
470                        let error = torsh_tensor_matmul(
471                            self.tensors[*a_idx],
472                            self.tensors[*b_idx],
473                            output_tensor,
474                        );
475                        if error != TorshError::Success {
476                            ptr::null_mut()
477                        } else {
478                            output_tensor
479                        }
480                    }
481                },
482                BatchOperation::ReLU { tensor_idx } => unsafe {
483                    // Create output tensor with same shape as first input
484                    let ndim = torsh_tensor_ndim(self.tensors[*tensor_idx]);
485                    if ndim == 0 {
486                        return Err(FfiError::Tensor {
487                            message: "Invalid tensor dimensions".to_string(),
488                        });
489                    }
490                    let mut shape_vec = vec![0usize; ndim];
491                    let mut ndim_out = ndim;
492                    torsh_tensor_shape(
493                        self.tensors[*tensor_idx],
494                        shape_vec.as_mut_ptr(),
495                        &mut ndim_out,
496                    );
497                    let output_tensor = torsh_tensor_zeros(shape_vec.as_ptr(), ndim);
498                    if output_tensor.is_null() {
499                        ptr::null_mut()
500                    } else {
501                        let error = torsh_tensor_relu(self.tensors[*tensor_idx], output_tensor);
502                        if error != TorshError::Success {
503                            ptr::null_mut()
504                        } else {
505                            output_tensor
506                        }
507                    }
508                },
509                BatchOperation::ScalarAdd { tensor_idx, scalar } => {
510                    // Note: Would need to implement scalar operations in C API
511                    unsafe { torsh_tensor_add_scalar(self.tensors[*tensor_idx], *scalar) }
512                }
513                BatchOperation::ScalarMul { tensor_idx, scalar } => {
514                    // Note: Would need to implement scalar operations in C API
515                    unsafe { torsh_tensor_mul_scalar(self.tensors[*tensor_idx], *scalar) }
516                }
517            };
518
519            if result.is_null() {
520                return Err(FfiError::Tensor {
521                    message: "Batch operation failed".to_string(),
522                });
523            }
524
525            results.push(result);
526        }
527
528        let duration = start.elapsed().as_millis() as u64;
529        PERF_STATS.record_operation(duration);
530
531        Ok(results)
532    }
533}
534
535/// Asynchronous operation queue for non-blocking operations
536pub struct AsyncOperationQueue {
537    queue: Arc<Mutex<VecDeque<AsyncOperation>>>,
538    max_queue_size: usize,
539}
540
541struct AsyncOperation {
542    operation: Box<dyn Fn() -> *mut TorshTensor + Send + Sync>,
543    callback: Option<Box<dyn Fn(*mut TorshTensor) + Send + Sync>>,
544    created_at: Instant,
545}
546
547impl AsyncOperationQueue {
548    pub fn new(max_queue_size: usize) -> Self {
549        Self {
550            queue: Arc::new(Mutex::new(VecDeque::new())),
551            max_queue_size,
552        }
553    }
554
555    pub fn enqueue<F, C>(&self, operation: F, callback: Option<C>) -> FfiResult<()>
556    where
557        F: Fn() -> *mut TorshTensor + Send + Sync + 'static,
558        C: Fn(*mut TorshTensor) + Send + Sync + 'static,
559    {
560        let mut queue = self.queue.lock();
561
562        if queue.len() >= self.max_queue_size {
563            return Err(FfiError::AllocationFailed {
564                message: "Async operation queue is full".to_string(),
565            });
566        }
567
568        queue.push_back(AsyncOperation {
569            operation: Box::new(operation),
570            callback: callback.map(|c| Box::new(c) as Box<dyn Fn(*mut TorshTensor) + Send + Sync>),
571            created_at: Instant::now(),
572        });
573
574        Ok(())
575    }
576
577    pub fn process_next(&self) -> FfiResult<bool> {
578        let operation = {
579            let mut queue = self.queue.lock();
580            queue.pop_front()
581        };
582
583        if let Some(op) = operation {
584            let start = Instant::now();
585            let result = (op.operation)();
586            let duration = start.elapsed().as_millis() as u64;
587
588            PERF_STATS.record_operation(duration);
589
590            if let Some(callback) = op.callback {
591                callback(result);
592            }
593
594            Ok(true)
595        } else {
596            Ok(false)
597        }
598    }
599
600    pub fn queue_size(&self) -> usize {
601        self.queue.lock().len()
602    }
603}
604
605/// Global async operation queue
606static ASYNC_QUEUE: std::sync::LazyLock<AsyncOperationQueue> =
607    std::sync::LazyLock::new(|| AsyncOperationQueue::new(10000));
608
609/// C API wrappers for performance optimizations
610
611/// Create a new batched operations context
612#[no_mangle]
613pub unsafe extern "C" fn torsh_batch_new() -> *mut BatchedOperations {
614    Box::into_raw(Box::new(BatchedOperations::new()))
615}
616
617/// Add a tensor to the batch context
618#[no_mangle]
619pub unsafe extern "C" fn torsh_batch_add_tensor(
620    batch: *mut BatchedOperations,
621    tensor: *mut TorshTensor,
622) -> c_int {
623    if batch.is_null() || tensor.is_null() {
624        return -1;
625    }
626
627    let batch_ref = &mut *batch;
628    batch_ref.add_tensor(tensor) as c_int
629}
630
631/// Add an addition operation to the batch
632#[no_mangle]
633pub unsafe extern "C" fn torsh_batch_add_add_op(
634    batch: *mut BatchedOperations,
635    a_idx: c_int,
636    b_idx: c_int,
637) -> TorshError {
638    if batch.is_null() {
639        return TorshError::InvalidArgument;
640    }
641
642    let batch_ref = &mut *batch;
643    batch_ref.add_operation(BatchOperation::Add {
644        a_idx: a_idx as usize,
645        b_idx: b_idx as usize,
646    });
647
648    TorshError::Success
649}
650
651/// Add a multiplication operation to the batch
652#[no_mangle]
653pub unsafe extern "C" fn torsh_batch_add_mul_op(
654    batch: *mut BatchedOperations,
655    a_idx: c_int,
656    b_idx: c_int,
657) -> TorshError {
658    if batch.is_null() {
659        return TorshError::InvalidArgument;
660    }
661
662    let batch_ref = &mut *batch;
663    batch_ref.add_operation(BatchOperation::Mul {
664        a_idx: a_idx as usize,
665        b_idx: b_idx as usize,
666    });
667
668    TorshError::Success
669}
670
671/// Add a ReLU operation to the batch
672#[no_mangle]
673pub unsafe extern "C" fn torsh_batch_add_relu_op(
674    batch: *mut BatchedOperations,
675    tensor_idx: c_int,
676) -> TorshError {
677    if batch.is_null() {
678        return TorshError::InvalidArgument;
679    }
680
681    let batch_ref = &mut *batch;
682    batch_ref.add_operation(BatchOperation::ReLU {
683        tensor_idx: tensor_idx as usize,
684    });
685
686    TorshError::Success
687}
688
689/// Execute all operations in the batch
690#[no_mangle]
691pub unsafe extern "C" fn torsh_batch_execute(
692    batch: *mut BatchedOperations,
693    results: *mut *mut TorshTensor,
694    max_results: c_int,
695    actual_results: *mut c_int,
696) -> TorshError {
697    if batch.is_null() || results.is_null() || actual_results.is_null() {
698        return TorshError::InvalidArgument;
699    }
700
701    let batch_ref = &*batch;
702    match batch_ref.execute_batch() {
703        Ok(result_tensors) => {
704            let copy_count = std::cmp::min(result_tensors.len(), max_results as usize);
705
706            for (i, tensor) in result_tensors.iter().take(copy_count).enumerate() {
707                *results.add(i) = *tensor;
708            }
709
710            *actual_results = copy_count as c_int;
711            TorshError::Success
712        }
713        Err(_) => TorshError::RuntimeError,
714    }
715}
716
717/// Free a batched operations context
718#[no_mangle]
719pub unsafe extern "C" fn torsh_batch_free(batch: *mut BatchedOperations) {
720    if !batch.is_null() {
721        let _ = Box::from_raw(batch);
722    }
723}
724
725/// Get current performance statistics
726#[no_mangle]
727pub unsafe extern "C" fn torsh_get_performance_stats(
728    total_ops: *mut u64,
729    avg_time_ms: *mut c_float,
730    cache_hit_rate: *mut c_float,
731) -> TorshError {
732    if total_ops.is_null() || avg_time_ms.is_null() || cache_hit_rate.is_null() {
733        return TorshError::InvalidArgument;
734    }
735
736    let snapshot = PERF_STATS.snapshot();
737    *total_ops = snapshot.total_operations;
738    *avg_time_ms = snapshot.avg_time_ms as c_float;
739    *cache_hit_rate = snapshot.cache_hit_rate() as c_float;
740    TorshError::Success
741}
742
743/// Reset performance statistics
744#[no_mangle]
745pub unsafe extern "C" fn torsh_reset_performance_stats() -> TorshError {
746    PERF_STATS.reset();
747    TorshError::Success
748}
749
750/// Process one item from the async operation queue
751#[no_mangle]
752pub unsafe extern "C" fn torsh_process_async_queue() -> c_int {
753    match ASYNC_QUEUE.process_next() {
754        Ok(true) => 1,  // Processed an operation
755        Ok(false) => 0, // Queue was empty
756        Err(_) => -1,   // Error
757    }
758}
759
760/// Get the current async queue size
761#[no_mangle]
762pub unsafe extern "C" fn torsh_async_queue_size() -> c_int {
763    ASYNC_QUEUE.queue_size() as c_int
764}
765
766/// Clear the operation cache
767#[no_mangle]
768pub unsafe extern "C" fn torsh_clear_operation_cache() -> TorshError {
769    {
770        let mut cache = OP_CACHE.cache.write();
771        cache.clear();
772    }
773    TorshError::Success
774}
775
776// =============================================================================
777// Memory Pool API Functions
778// =============================================================================
779
780/// Get memory pool statistics
781#[no_mangle]
782pub unsafe extern "C" fn torsh_get_memory_pool_stats(
783    total_pools: *mut usize,
784    total_buffers: *mut usize,
785    memory_usage: *mut usize,
786) -> TorshError {
787    if total_pools.is_null() || total_buffers.is_null() || memory_usage.is_null() {
788        return TorshError::InvalidArgument;
789    }
790
791    let stats = MEMORY_POOL.stats();
792    *total_pools = stats.total_pools;
793    *total_buffers = stats.total_buffers;
794    *memory_usage = stats.memory_usage;
795
796    TorshError::Success
797}
798
799/// Clean up unused memory pool buffers
800#[no_mangle]
801pub unsafe extern "C" fn torsh_cleanup_memory_pool() -> TorshError {
802    MEMORY_POOL.cleanup();
803    TorshError::Success
804}
805
806/// Get a buffer from the memory pool (for internal use by other modules)
807pub fn get_pooled_buffer(size: usize) -> Vec<f32> {
808    MEMORY_POOL.get_buffer(size)
809}
810
811/// Return a buffer to the memory pool (for internal use by other modules)
812pub fn return_pooled_buffer(buffer: Vec<f32>) {
813    MEMORY_POOL.return_buffer(buffer);
814}
815
816/// Get current performance statistics snapshot (Rust API)
817pub fn get_performance_stats() -> PerformanceStatsSnapshot {
818    PERF_STATS.snapshot()
819}
820
821/// Advanced performance profiler for fine-grained analysis
822#[derive(Debug, Default)]
823pub struct AdvancedProfiler {
824    operation_timings: RwLock<std::collections::HashMap<String, Vec<u64>>>,
825    memory_snapshots: RwLock<Vec<(String, usize, Instant)>>,
826}
827
828impl AdvancedProfiler {
829    pub fn new() -> Self {
830        Self::default()
831    }
832
833    /// Record timing for a specific operation type
834    pub fn record_timing(&self, operation: &str, duration_ms: u64) {
835        let mut timings = self.operation_timings.write();
836        timings
837            .entry(operation.to_string())
838            .or_default()
839            .push(duration_ms);
840    }
841
842    /// Record memory usage snapshot
843    pub fn record_memory_snapshot(&self, label: &str, memory_bytes: usize) {
844        let mut snapshots = self.memory_snapshots.write();
845        snapshots.push((label.to_string(), memory_bytes, Instant::now()));
846
847        // Keep only recent snapshots
848        if snapshots.len() > 1000 {
849            snapshots.drain(0..500);
850        }
851    }
852
853    /// Get timing statistics for an operation
854    pub fn get_timing_stats(&self, operation: &str) -> Option<TimingStats> {
855        let timings = self.operation_timings.read();
856        if let Some(times) = timings.get(operation) {
857            if times.is_empty() {
858                return None;
859            }
860
861            let mut sorted_times = times.clone();
862            sorted_times.sort_unstable();
863
864            let sum: u64 = times.iter().sum();
865            let count = times.len();
866            let min = *sorted_times.first().unwrap();
867            let max = *sorted_times.last().unwrap();
868            let avg = sum as f64 / count as f64;
869
870            let median = if count % 2 == 0 {
871                (sorted_times[count / 2 - 1] + sorted_times[count / 2]) as f64 / 2.0
872            } else {
873                sorted_times[count / 2] as f64
874            };
875
876            let p95_idx = (count as f64 * 0.95).ceil() as usize - 1;
877            let p95 = sorted_times[p95_idx.min(count - 1)];
878
879            Some(TimingStats {
880                operation: operation.to_string(),
881                count,
882                min,
883                max,
884                avg,
885                median,
886                p95,
887            })
888        } else {
889            None
890        }
891    }
892
893    /// Get memory usage over time
894    pub fn get_memory_usage_trend(&self) -> Vec<(String, usize, std::time::Duration)> {
895        let snapshots = self.memory_snapshots.read();
896        let start_time = snapshots
897            .first()
898            .map(|(_, _, t)| *t)
899            .unwrap_or_else(Instant::now);
900
901        snapshots
902            .iter()
903            .map(|(label, memory, time)| (label.clone(), *memory, time.duration_since(start_time)))
904            .collect()
905    }
906}
907
908#[derive(Debug, Clone)]
909pub struct TimingStats {
910    pub operation: String,
911    pub count: usize,
912    pub min: u64,
913    pub max: u64,
914    pub avg: f64,
915    pub median: f64,
916    pub p95: u64,
917}
918
919/// Global advanced profiler
920static ADVANCED_PROFILER: std::sync::LazyLock<AdvancedProfiler> =
921    std::sync::LazyLock::new(AdvancedProfiler::new);
922
923/// Record a profiling measurement
924pub fn profile_operation<F, R>(operation: &str, f: F) -> R
925where
926    F: FnOnce() -> R,
927{
928    let start = Instant::now();
929    let result = f();
930    let duration = start.elapsed().as_millis() as u64;
931
932    ADVANCED_PROFILER.record_timing(operation, duration);
933
934    PERF_STATS.record_operation(duration);
935
936    result
937}
938
939/// Get profiling statistics for an operation (C API)
940#[no_mangle]
941pub unsafe extern "C" fn torsh_get_operation_timing_stats(
942    operation: *const c_char,
943    count: *mut usize,
944    min_ms: *mut u64,
945    max_ms: *mut u64,
946    avg_ms: *mut c_float,
947) -> TorshError {
948    if operation.is_null()
949        || count.is_null()
950        || min_ms.is_null()
951        || max_ms.is_null()
952        || avg_ms.is_null()
953    {
954        return TorshError::InvalidArgument;
955    }
956
957    let operation_str = match std::ffi::CStr::from_ptr(operation).to_str() {
958        Ok(s) => s,
959        Err(_) => return TorshError::InvalidArgument,
960    };
961
962    if let Some(stats) = ADVANCED_PROFILER.get_timing_stats(operation_str) {
963        *count = stats.count;
964        *min_ms = stats.min;
965        *max_ms = stats.max;
966        *avg_ms = stats.avg as c_float;
967        TorshError::Success
968    } else {
969        TorshError::NotImplemented
970    }
971}
972
973#[cfg(test)]
974mod tests {
975    use super::*;
976
977    #[test]
978    fn test_performance_stats() {
979        let stats = PerformanceStats::new();
980
981        stats.record_operation(100);
982        stats.record_operation(200);
983        stats.record_operation(150);
984
985        assert_eq!(stats.total_operations.load(Ordering::Relaxed), 3);
986        assert_eq!(stats.avg_time_ms(), 150.0);
987        assert_eq!(stats.min_time_ms.load(Ordering::Relaxed), 100);
988        assert_eq!(stats.max_time_ms.load(Ordering::Relaxed), 200);
989    }
990
991    #[test]
992    fn test_cache_hit_rate() {
993        let stats = PerformanceStats::new();
994
995        stats.record_cache_hit();
996        stats.record_cache_hit();
997        stats.record_cache_miss();
998
999        assert_eq!(stats.cache_hit_rate(), 2.0 / 3.0);
1000    }
1001
1002    #[test]
1003    fn test_operation_cache() {
1004        let cache = OperationCache::new(10, 1000);
1005
1006        let key = "test_op".to_string();
1007        let value = vec![1, 2, 3, 4];
1008
1009        cache.put(key.clone(), value.clone());
1010
1011        let retrieved = cache.get(&key);
1012        assert_eq!(retrieved, Some(value));
1013
1014        let non_existent = cache.get("non_existent");
1015        assert_eq!(non_existent, None);
1016    }
1017
1018    #[test]
1019    fn test_batched_operations() {
1020        let mut batch = BatchedOperations::new();
1021
1022        // Note: These would be real tensor pointers in practice
1023        let tensor1 = 0x12345 as *mut TorshTensor;
1024        let tensor2 = 0x67890 as *mut TorshTensor;
1025
1026        let idx1 = batch.add_tensor(tensor1);
1027        let idx2 = batch.add_tensor(tensor2);
1028
1029        assert_eq!(idx1, 0);
1030        assert_eq!(idx2, 1);
1031
1032        batch.add_operation(BatchOperation::Add {
1033            a_idx: idx1,
1034            b_idx: idx2,
1035        });
1036        batch.add_operation(BatchOperation::ReLU { tensor_idx: idx1 });
1037
1038        assert_eq!(batch.operations.len(), 2);
1039    }
1040
1041    #[test]
1042    fn test_async_operation_queue() {
1043        let queue = AsyncOperationQueue::new(10);
1044
1045        let operation = || 0x12345 as *mut TorshTensor;
1046        let callback = |_tensor: *mut TorshTensor| {
1047            // Callback logic would go here
1048        };
1049
1050        let result = queue.enqueue(operation, Some(callback));
1051        assert!(result.is_ok());
1052
1053        assert_eq!(queue.queue_size(), 1);
1054
1055        let processed = queue.process_next();
1056        assert!(processed.is_ok());
1057        assert_eq!(processed.unwrap(), true);
1058
1059        assert_eq!(queue.queue_size(), 0);
1060    }
1061}