1#![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#[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 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 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 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 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 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#[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
174static PERF_STATS: std::sync::LazyLock<Arc<PerformanceStats>> =
176 std::sync::LazyLock::new(|| Arc::new(PerformanceStats::new()));
177
178#[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>, 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 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 cache.len() >= self.max_size {
225 let mut entries: Vec<_> = cache.iter().collect();
226 entries.sort_by_key(|(_, v)| v.created_at);
227
228 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 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
253static OP_CACHE: std::sync::LazyLock<OperationCache> =
255 std::sync::LazyLock::new(|| OperationCache::new(1000, 300000)); #[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 pub fn get_buffer(&self, size: usize) -> Vec<f32> {
276 if size > self.max_buffer_size {
277 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 Vec::with_capacity(size)
295 }
296
297 pub fn return_buffer(&self, mut buffer: Vec<f32>) {
299 let size = buffer.capacity();
300 if size > self.max_buffer_size {
301 return; }
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 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 pub fn cleanup(&self) {
338 let mut pools = self.pools.write();
339 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
356static MEMORY_POOL: std::sync::LazyLock<TensorMemoryPool> =
358 std::sync::LazyLock::new(|| TensorMemoryPool::new(50, 1_000_000)); #[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 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 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 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 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 unsafe { torsh_tensor_add_scalar(self.tensors[*tensor_idx], *scalar) }
512 }
513 BatchOperation::ScalarMul { tensor_idx, scalar } => {
514 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
535pub 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
605static ASYNC_QUEUE: std::sync::LazyLock<AsyncOperationQueue> =
607 std::sync::LazyLock::new(|| AsyncOperationQueue::new(10000));
608
609#[no_mangle]
613pub unsafe extern "C" fn torsh_batch_new() -> *mut BatchedOperations {
614 Box::into_raw(Box::new(BatchedOperations::new()))
615}
616
617#[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#[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#[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#[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#[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#[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#[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#[no_mangle]
745pub unsafe extern "C" fn torsh_reset_performance_stats() -> TorshError {
746 PERF_STATS.reset();
747 TorshError::Success
748}
749
750#[no_mangle]
752pub unsafe extern "C" fn torsh_process_async_queue() -> c_int {
753 match ASYNC_QUEUE.process_next() {
754 Ok(true) => 1, Ok(false) => 0, Err(_) => -1, }
758}
759
760#[no_mangle]
762pub unsafe extern "C" fn torsh_async_queue_size() -> c_int {
763 ASYNC_QUEUE.queue_size() as c_int
764}
765
766#[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#[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#[no_mangle]
801pub unsafe extern "C" fn torsh_cleanup_memory_pool() -> TorshError {
802 MEMORY_POOL.cleanup();
803 TorshError::Success
804}
805
806pub fn get_pooled_buffer(size: usize) -> Vec<f32> {
808 MEMORY_POOL.get_buffer(size)
809}
810
811pub fn return_pooled_buffer(buffer: Vec<f32>) {
813 MEMORY_POOL.return_buffer(buffer);
814}
815
816pub fn get_performance_stats() -> PerformanceStatsSnapshot {
818 PERF_STATS.snapshot()
819}
820
821#[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 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 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 if snapshots.len() > 1000 {
849 snapshots.drain(0..500);
850 }
851 }
852
853 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 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
919static ADVANCED_PROFILER: std::sync::LazyLock<AdvancedProfiler> =
921 std::sync::LazyLock::new(AdvancedProfiler::new);
922
923pub 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#[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 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 };
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}