1use scirs2_core::ndarray::Array2;
11use sklears_core::{
12 error::{Result, SklearsError},
13 types::Float,
14};
15use std::alloc::{alloc, dealloc, Layout};
16use std::ptr;
17
18#[allow(dead_code)]
20const CACHE_LINE_SIZE: usize = 64;
21
22#[allow(dead_code)]
24const SIMD_ALIGNMENT: usize = 32;
25
26#[derive(Debug, Clone)]
28pub struct CacheConfig {
29 pub tile_size: usize,
31
32 pub prefetch_distance: usize,
34
35 pub use_alignment: bool,
37
38 pub block_size: usize,
40}
41
42impl Default for CacheConfig {
43 fn default() -> Self {
44 Self {
45 tile_size: 64,
46 prefetch_distance: 8,
47 use_alignment: true,
48 block_size: 128,
49 }
50 }
51}
52
53pub struct CacheFriendlyMatrix {
55 data: Vec<Float>,
56 nrows: usize,
57 ncols: usize,
58 layout: MatrixLayout,
59 config: CacheConfig,
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64pub enum MatrixLayout {
65 RowMajor,
67
68 ColumnMajor,
70
71 Blocked,
73}
74
75impl CacheFriendlyMatrix {
76 pub fn new(array: Array2<Float>, layout: MatrixLayout, config: CacheConfig) -> Self {
78 let (nrows, ncols) = array.dim();
79
80 let data = match layout {
81 MatrixLayout::RowMajor => array.into_raw_vec_and_offset().0,
82 MatrixLayout::ColumnMajor => {
83 let mut data = Vec::with_capacity(nrows * ncols);
85 for j in 0..ncols {
86 for i in 0..nrows {
87 data.push(array[[i, j]]);
88 }
89 }
90 data
91 }
92 MatrixLayout::Blocked => {
93 Self::convert_to_blocked(&array, config.block_size)
95 }
96 };
97
98 Self {
99 data,
100 nrows,
101 ncols,
102 layout,
103 config,
104 }
105 }
106
107 fn convert_to_blocked(array: &Array2<Float>, block_size: usize) -> Vec<Float> {
109 let (nrows, ncols) = array.dim();
110 let mut blocked = Vec::with_capacity(nrows * ncols);
111
112 let n_row_blocks = nrows.div_ceil(block_size);
113 let n_col_blocks = ncols.div_ceil(block_size);
114
115 for block_i in 0..n_row_blocks {
116 for block_j in 0..n_col_blocks {
117 let row_start = block_i * block_size;
118 let row_end = (row_start + block_size).min(nrows);
119 let col_start = block_j * block_size;
120 let col_end = (col_start + block_size).min(ncols);
121
122 for i in row_start..row_end {
123 for j in col_start..col_end {
124 blocked.push(array[[i, j]]);
125 }
126 }
127
128 let block_elements = (row_end - row_start) * (col_end - col_start);
130 let full_block = block_size * block_size;
131 if full_block > block_elements {
132 blocked.resize(blocked.len() + (full_block - block_elements), 0.0);
133 }
134 }
135 }
136
137 blocked
138 }
139
140 pub fn get(&self, i: usize, j: usize) -> Float {
142 match self.layout {
143 MatrixLayout::RowMajor => self.data[i * self.ncols + j],
144 MatrixLayout::ColumnMajor => self.data[j * self.nrows + i],
145 MatrixLayout::Blocked => {
146 let block_size = self.config.block_size;
147 let block_i = i / block_size;
148 let block_j = j / block_size;
149 let local_i = i % block_size;
150 let local_j = j % block_size;
151
152 let n_col_blocks = self.ncols.div_ceil(block_size);
153 let block_idx = block_i * n_col_blocks + block_j;
154 let block_start = block_idx * block_size * block_size;
155 let offset = local_i * block_size + local_j;
156
157 self.data[block_start + offset]
158 }
159 }
160 }
161
162 pub fn to_array(&self) -> Array2<Float> {
164 let mut array = Array2::zeros((self.nrows, self.ncols));
165
166 for i in 0..self.nrows {
167 for j in 0..self.ncols {
168 array[[i, j]] = self.get(i, j);
169 }
170 }
171
172 array
173 }
174
175 pub fn matmul(&self, other: &Self) -> Result<Self> {
177 if self.ncols != other.nrows {
178 return Err(SklearsError::InvalidInput(format!(
179 "Dimension mismatch: {} x {} cannot multiply {} x {}",
180 self.nrows, self.ncols, other.nrows, other.ncols
181 )));
182 }
183
184 let mut result_data = vec![0.0; self.nrows * other.ncols];
185
186 let tile_size = self.config.tile_size;
188
189 for i_tile in (0..self.nrows).step_by(tile_size) {
190 for j_tile in (0..other.ncols).step_by(tile_size) {
191 for k_tile in (0..self.ncols).step_by(tile_size) {
192 let i_end = (i_tile + tile_size).min(self.nrows);
194 let j_end = (j_tile + tile_size).min(other.ncols);
195 let k_end = (k_tile + tile_size).min(self.ncols);
196
197 for i in i_tile..i_end {
198 for j in j_tile..j_end {
199 let mut sum = 0.0;
200 for k in k_tile..k_end {
201 sum += self.get(i, k) * other.get(k, j);
202 }
203 let idx = i * other.ncols + j;
204 result_data[idx] += sum;
205 }
206 }
207 }
208 }
209 }
210
211 Ok(Self {
212 data: result_data,
213 nrows: self.nrows,
214 ncols: other.ncols,
215 layout: MatrixLayout::RowMajor,
216 config: self.config.clone(),
217 })
218 }
219
220 pub fn dim(&self) -> (usize, usize) {
222 (self.nrows, self.ncols)
223 }
224}
225
226pub struct MemoryPool {
228 buffers: Vec<Vec<Float>>,
230
231 buffer_size: usize,
233
234 max_pool_size: usize,
236}
237
238impl MemoryPool {
239 pub fn new(buffer_size: usize, max_pool_size: usize) -> Self {
241 Self {
242 buffers: Vec::new(),
243 buffer_size,
244 max_pool_size,
245 }
246 }
247
248 pub fn acquire(&mut self) -> Vec<Float> {
250 self.buffers
251 .pop()
252 .unwrap_or_else(|| Vec::with_capacity(self.buffer_size))
253 }
254
255 pub fn release(&mut self, mut buffer: Vec<Float>) {
257 if self.buffers.len() < self.max_pool_size {
258 buffer.clear();
259 self.buffers.push(buffer);
260 }
261 }
263
264 pub fn clear(&mut self) {
266 self.buffers.clear();
267 }
268
269 pub fn stats(&self) -> PoolStats {
271 PoolStats {
272 available_buffers: self.buffers.len(),
273 buffer_size: self.buffer_size,
274 total_allocated_bytes: self.buffers.len()
275 * self.buffer_size
276 * std::mem::size_of::<Float>(),
277 }
278 }
279}
280
281#[derive(Debug, Clone)]
283pub struct PoolStats {
284 pub available_buffers: usize,
285 pub buffer_size: usize,
286 pub total_allocated_bytes: usize,
287}
288
289pub struct AlignedAllocator;
291
292impl AlignedAllocator {
293 pub fn allocate(size: usize, alignment: usize) -> Result<*mut Float> {
295 if size == 0 {
296 return Ok(ptr::null_mut());
297 }
298
299 unsafe {
300 let layout = Layout::from_size_align(size * std::mem::size_of::<Float>(), alignment)
301 .map_err(|e| SklearsError::InvalidInput(format!("Invalid layout: {}", e)))?;
302
303 let ptr = alloc(layout) as *mut Float;
304
305 if ptr.is_null() {
306 return Err(SklearsError::InvalidInput("Allocation failed".to_string()));
307 }
308
309 Ok(ptr)
310 }
311 }
312
313 pub unsafe fn deallocate(ptr: *mut Float, size: usize, alignment: usize) {
320 if ptr.is_null() || size == 0 {
321 return;
322 }
323
324 let layout =
325 Layout::from_size_align_unchecked(size * std::mem::size_of::<Float>(), alignment);
326
327 dealloc(ptr as *mut u8, layout);
328 }
329
330 pub fn aligned_vec(
343 size: usize,
344 alignment: usize,
345 ) -> Result<crate::hardware_acceleration::AlignedBuffer> {
346 Ok(crate::hardware_acceleration::AlignedBuffer::new(
347 size, alignment,
348 ))
349 }
350}
351
352#[inline(always)]
354pub fn prefetch_hint<T>(_addr: *const T) {
355 #[cfg(target_arch = "x86_64")]
356 {
357 unsafe {
358 #[cfg(target_feature = "sse")]
359 {
360 std::arch::x86_64::_mm_prefetch::<{ std::arch::x86_64::_MM_HINT_T0 }>(
361 _addr as *const i8,
362 );
363 }
364 }
365 }
366 }
368
369pub struct PerformanceProfiler {
371 measurements: Vec<Measurement>,
372}
373
374#[derive(Debug, Clone)]
375pub struct Measurement {
376 pub name: String,
377 pub duration_ns: u128,
378 pub memory_bytes: usize,
379}
380
381impl PerformanceProfiler {
382 pub fn new() -> Self {
384 Self {
385 measurements: Vec::new(),
386 }
387 }
388
389 pub fn start(&mut self, name: &str) -> TimingGuard {
391 TimingGuard {
392 name: name.to_string(),
393 start: std::time::Instant::now(),
394 }
395 }
396
397 pub fn record(&mut self, name: String, duration_ns: u128, memory_bytes: usize) {
399 self.measurements.push(Measurement {
400 name,
401 duration_ns,
402 memory_bytes,
403 });
404 }
405
406 pub fn measurements(&self) -> &[Measurement] {
408 &self.measurements
409 }
410
411 pub fn clear(&mut self) {
413 self.measurements.clear();
414 }
415
416 pub fn report(&self) -> String {
418 let mut report = String::from("Performance Report:\n");
419 report.push_str("===================\n\n");
420
421 for m in &self.measurements {
422 report.push_str(&format!(
423 "{}: {:.2} ms, {} bytes\n",
424 m.name,
425 m.duration_ns as f64 / 1_000_000.0,
426 m.memory_bytes
427 ));
428 }
429
430 let total_time: u128 = self.measurements.iter().map(|m| m.duration_ns).sum();
431 let total_memory: usize = self.measurements.iter().map(|m| m.memory_bytes).sum();
432
433 report.push_str(&format!(
434 "\nTotal: {:.2} ms, {} bytes\n",
435 total_time as f64 / 1_000_000.0,
436 total_memory
437 ));
438
439 report
440 }
441}
442
443impl Default for PerformanceProfiler {
444 fn default() -> Self {
445 Self::new()
446 }
447}
448
449pub struct TimingGuard {
451 name: String,
452 start: std::time::Instant,
453}
454
455impl TimingGuard {
456 pub fn finish(self) -> (String, u128) {
458 let duration = self.start.elapsed().as_nanos();
459 (self.name, duration)
460 }
461}
462
463#[cfg(test)]
464mod tests {
465 use super::*;
466
467 #[test]
468 fn test_cache_friendly_matrix_creation() {
469 let array =
470 Array2::from_shape_vec((3, 3), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0])
471 .expect("operation should succeed");
472
473 let cache_matrix = CacheFriendlyMatrix::new(
474 array.clone(),
475 MatrixLayout::RowMajor,
476 CacheConfig::default(),
477 );
478
479 assert_eq!(cache_matrix.dim(), (3, 3));
480 assert_eq!(cache_matrix.get(0, 0), 1.0);
481 assert_eq!(cache_matrix.get(2, 2), 9.0);
482 }
483
484 #[test]
485 fn test_cache_friendly_matmul() {
486 let a = Array2::from_shape_vec((2, 3), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
487 .expect("shape and data length should match");
488 let b = Array2::from_shape_vec((3, 2), vec![7.0, 8.0, 9.0, 10.0, 11.0, 12.0])
489 .expect("shape and data length should match");
490
491 let cache_a = CacheFriendlyMatrix::new(a, MatrixLayout::RowMajor, CacheConfig::default());
492 let cache_b = CacheFriendlyMatrix::new(b, MatrixLayout::RowMajor, CacheConfig::default());
493
494 let result = cache_a.matmul(&cache_b).expect("operation should succeed");
495 assert_eq!(result.dim(), (2, 2));
496
497 assert!((result.get(0, 0) - 58.0).abs() < 1e-6);
499 assert!((result.get(0, 1) - 64.0).abs() < 1e-6);
500 }
501
502 #[test]
503 fn test_memory_pool() {
504 let mut pool = MemoryPool::new(100, 5);
505
506 let buf1 = pool.acquire();
508 let buf2 = pool.acquire();
509
510 pool.release(buf1);
511 pool.release(buf2);
512
513 let stats = pool.stats();
514 assert_eq!(stats.available_buffers, 2);
515
516 let _buf3 = pool.acquire();
518 let stats = pool.stats();
519 assert_eq!(stats.available_buffers, 1);
520 }
521
522 #[test]
523 fn test_performance_profiler() {
524 let mut profiler = PerformanceProfiler::new();
525
526 let guard = profiler.start("test_operation");
527 std::thread::sleep(std::time::Duration::from_millis(10));
528 let (name, duration) = guard.finish();
529
530 profiler.record(name, duration, 1024);
531
532 let measurements = profiler.measurements();
533 assert_eq!(measurements.len(), 1);
534 assert!(measurements[0].duration_ns > 0);
535 }
536
537 #[test]
538 fn test_column_major_layout() {
539 let array = Array2::from_shape_vec((2, 2), vec![1.0, 2.0, 3.0, 4.0])
540 .expect("shape and data length should match");
541
542 let cache_matrix = CacheFriendlyMatrix::new(
543 array.clone(),
544 MatrixLayout::ColumnMajor,
545 CacheConfig::default(),
546 );
547
548 assert_eq!(cache_matrix.get(0, 0), 1.0);
549 assert_eq!(cache_matrix.get(0, 1), 2.0);
550 assert_eq!(cache_matrix.get(1, 0), 3.0);
551 assert_eq!(cache_matrix.get(1, 1), 4.0);
552 }
553
554 #[test]
555 fn test_profiler_report() {
556 let mut profiler = PerformanceProfiler::new();
557
558 profiler.record("op1".to_string(), 1_000_000, 1024);
559 profiler.record("op2".to_string(), 2_000_000, 2048);
560
561 let report = profiler.report();
562 assert!(report.contains("op1"));
563 assert!(report.contains("op2"));
564 assert!(report.contains("Total"));
565 }
566}