1pub struct CacheOptimizer {
8 cache_line_size: usize,
9 l1_cache_size: usize,
10 l2_cache_size: usize,
11 tlb_page_size: usize,
12}
13
14impl CacheOptimizer {
15 pub fn new() -> Self {
17 Self {
18 cache_line_size: Self::detect_cache_line_size(),
19 l1_cache_size: Self::detect_l1_cache_size(),
20 l2_cache_size: Self::detect_l2_cache_size(),
21 tlb_page_size: Self::detect_page_size(),
22 }
23 }
24
25 fn detect_cache_line_size() -> usize {
27 64
30 }
31
32 fn detect_l1_cache_size() -> usize {
34 32 * 1024 }
36
37 fn detect_l2_cache_size() -> usize {
39 256 * 1024 }
41
42 fn detect_page_size() -> usize {
44 4096 }
46
47 pub fn get_optimal_alignment(&self, data_size: usize) -> usize {
49 if data_size <= self.cache_line_size {
50 self.cache_line_size
52 } else if data_size <= self.tlb_page_size {
53 self.tlb_page_size
55 } else {
56 ((data_size + self.tlb_page_size - 1) / self.tlb_page_size) * self.tlb_page_size
58 }
59 }
60
61 pub fn get_optimal_block_size(&self, element_size: usize, total_elements: usize) -> usize {
63 let target_bytes = self.l1_cache_size / 2; let elements_per_block = target_bytes / element_size;
65
66 elements_per_block
68 .max(64) .min(total_elements) .min(8192) }
72
73 pub fn optimize_access_pattern(&self, dims: &[usize], element_size: usize) -> AccessPattern {
75 let total_size = dims.iter().product::<usize>() * element_size;
76
77 if total_size <= self.l1_cache_size {
78 AccessPattern::Sequential } else if total_size <= self.l2_cache_size {
80 AccessPattern::Blocked {
81 block_size: self.get_optimal_block_size(element_size, dims[dims.len() - 1]),
82 }
83 } else {
84 AccessPattern::Tiled {
85 tile_dims: self.calculate_optimal_tile_size(dims, element_size),
86 }
87 }
88 }
89
90 fn calculate_optimal_tile_size(&self, dims: &[usize], element_size: usize) -> Vec<usize> {
92 let target_bytes = self.l2_cache_size / 3; let mut tile_dims = Vec::with_capacity(dims.len());
94
95 for &dim_size in dims {
96 let max_tile_elements = target_bytes / element_size;
97 let optimal_tile_size =
98 (max_tile_elements as f64).powf(1.0 / dims.len() as f64) as usize;
99 tile_dims.push(optimal_tile_size.min(dim_size).max(8)); }
101
102 tile_dims
103 }
104
105 pub fn cache_line_size(&self) -> usize {
107 self.cache_line_size
108 }
109
110 pub fn l1_cache_size(&self) -> usize {
112 self.l1_cache_size
113 }
114
115 pub fn l2_cache_size(&self) -> usize {
117 self.l2_cache_size
118 }
119
120 pub fn page_size(&self) -> usize {
122 self.tlb_page_size
123 }
124}
125
126impl Default for CacheOptimizer {
127 fn default() -> Self {
128 Self::new()
129 }
130}
131
132#[derive(Debug, Clone)]
134pub enum AccessPattern {
135 Sequential,
136 Blocked { block_size: usize },
137 Tiled { tile_dims: Vec<usize> },
138}
139
140pub struct MatrixLayoutOptimizer {
142 optimizer: CacheOptimizer,
143}
144
145impl MatrixLayoutOptimizer {
146 pub fn new() -> Self {
148 Self {
149 optimizer: CacheOptimizer::new(),
150 }
151 }
152
153 pub fn to_blocked_layout<T: Copy>(
155 &self,
156 data: &[T],
157 rows: usize,
158 cols: usize,
159 block_size: usize,
160 ) -> Vec<T> {
161 let mut blocked_data = Vec::with_capacity(data.len());
162
163 for block_row in (0..rows).step_by(block_size) {
165 for block_col in (0..cols).step_by(block_size) {
166 for row in block_row..(block_row + block_size).min(rows) {
168 for col in block_col..(block_col + block_size).min(cols) {
169 let index = row * cols + col;
170 blocked_data.push(data[index]);
171 }
172 }
173 }
174 }
175
176 blocked_data
177 }
178
179 pub fn from_blocked_layout<T: Copy>(
181 &self,
182 blocked_data: &[T],
183 rows: usize,
184 cols: usize,
185 block_size: usize,
186 ) -> Vec<T> {
187 let mut data = vec![blocked_data[0]; blocked_data.len()]; let mut blocked_idx = 0;
189
190 for block_row in (0..rows).step_by(block_size) {
192 for block_col in (0..cols).step_by(block_size) {
193 for row in block_row..(block_row + block_size).min(rows) {
195 for col in block_col..(block_col + block_size).min(cols) {
196 let index = row * cols + col;
197 data[index] = blocked_data[blocked_idx];
198 blocked_idx += 1;
199 }
200 }
201 }
202 }
203
204 data
205 }
206
207 pub fn suggest_gemm_blocking(
209 &self,
210 m: usize,
211 n: usize,
212 k: usize,
213 element_size: usize,
214 ) -> (usize, usize, usize) {
215 let target_bytes = self.optimizer.l2_cache_size / 3; let target_elements = target_bytes / element_size;
217
218 let mk_block = (target_elements / n).max(8).min(m);
220 let nk_block = (target_elements / m).max(8).min(n);
221 let k_block = (target_elements / (mk_block + nk_block)).max(8).min(k);
222
223 (mk_block, nk_block, k_block)
224 }
225}
226
227impl Default for MatrixLayoutOptimizer {
228 fn default() -> Self {
229 Self::new()
230 }
231}
232
233pub struct PrefetchOptimizer {
235 prefetch_distance: usize,
236 stride_threshold: usize,
237}
238
239impl PrefetchOptimizer {
240 pub fn new() -> Self {
242 Self {
243 prefetch_distance: 64, stride_threshold: 4, }
246 }
247
248 pub fn prefetch_sequential<T>(&self, data: &[T], current_index: usize) {
250 let prefetch_index = current_index + self.prefetch_distance;
251 if prefetch_index < data.len() {
252 let _prefetch_ptr = &data[prefetch_index] as *const T;
256 }
257 }
258
259 pub fn prefetch_strided<T>(&self, data: &[T], current_index: usize, stride: usize) {
261 if stride >= self.stride_threshold {
262 let prefetch_index = current_index + stride * self.prefetch_distance;
263 if prefetch_index < data.len() {
264 let _prefetch_ptr = &data[prefetch_index] as *const T;
266 }
267 }
268 }
269
270 pub fn should_prefetch(&self, access_stride: usize, data_size: usize) -> bool {
272 data_size > 1024 * 1024 || access_stride >= self.stride_threshold
277 }
278}
279
280impl Default for PrefetchOptimizer {
281 fn default() -> Self {
282 Self::new()
283 }
284}
285
286static GLOBAL_CACHE_OPTIMIZER: std::sync::OnceLock<CacheOptimizer> = std::sync::OnceLock::new();
288
289pub fn global_cache_optimizer() -> &'static CacheOptimizer {
291 GLOBAL_CACHE_OPTIMIZER.get_or_init(CacheOptimizer::new)
292}
293
294pub fn align_to_cache_line(size: usize) -> usize {
296 let cache_line_size = global_cache_optimizer().cache_line_size;
297 (size + cache_line_size - 1) & !(cache_line_size - 1)
298}
299
300pub fn is_cache_aligned(ptr: *const u8) -> bool {
302 let cache_line_size = global_cache_optimizer().cache_line_size;
303 (ptr as usize) & (cache_line_size - 1) == 0
304}
305
306#[cfg(test)]
307mod tests {
308 use super::*;
309
310 #[test]
311 fn test_cache_optimizer_creation() {
312 let optimizer = CacheOptimizer::new();
313 assert_eq!(optimizer.cache_line_size(), 64);
314 assert_eq!(optimizer.l1_cache_size(), 32 * 1024);
315 assert_eq!(optimizer.l2_cache_size(), 256 * 1024);
316 assert_eq!(optimizer.page_size(), 4096);
317 }
318
319 #[test]
320 fn test_optimal_alignment() {
321 let optimizer = CacheOptimizer::new();
322
323 assert_eq!(optimizer.get_optimal_alignment(32), 64);
325
326 assert_eq!(optimizer.get_optimal_alignment(1024), 4096);
328
329 let large_size = 8192;
331 let alignment = optimizer.get_optimal_alignment(large_size);
332 assert!(alignment >= large_size);
333 assert_eq!(alignment % 4096, 0);
334 }
335
336 #[test]
337 fn test_optimal_block_size() {
338 let optimizer = CacheOptimizer::new();
339
340 let block_size = optimizer.get_optimal_block_size(4, 1000);
341 assert!(block_size >= 64); assert!(block_size <= 1000); assert!(block_size <= 8192); }
345
346 #[test]
347 fn test_access_pattern_optimization() {
348 let optimizer = CacheOptimizer::new();
349
350 let pattern = optimizer.optimize_access_pattern(&[100, 100], 4);
352 matches!(pattern, AccessPattern::Sequential);
353
354 let pattern = optimizer.optimize_access_pattern(&[1000, 1000], 4);
356 matches!(pattern, AccessPattern::Tiled { .. });
357 }
358
359 #[test]
360 fn test_tile_size_calculation() {
361 let optimizer = CacheOptimizer::new();
362 let dims = vec![1000, 1000];
363 let tile_dims = optimizer.calculate_optimal_tile_size(&dims, 4);
364
365 assert_eq!(tile_dims.len(), 2);
366 for &tile_dim in &tile_dims {
367 assert!(tile_dim >= 8); assert!(tile_dim <= 1000); }
370 }
371
372 #[test]
373 fn test_matrix_layout_optimizer() {
374 let optimizer = MatrixLayoutOptimizer::new();
375
376 let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; let blocked = optimizer.to_blocked_layout(&data, 3, 4, 2);
379
380 assert_eq!(blocked.len(), data.len());
382
383 let restored = optimizer.from_blocked_layout(&blocked, 3, 4, 2);
385 assert_eq!(restored, data);
386 }
387
388 #[test]
389 fn test_gemm_blocking_suggestion() {
390 let optimizer = MatrixLayoutOptimizer::new();
391 let (mb, nb, kb) = optimizer.suggest_gemm_blocking(1000, 1000, 1000, 4);
392
393 assert!(mb >= 8 && mb <= 1000);
395 assert!(nb >= 8 && nb <= 1000);
396 assert!(kb >= 8 && kb <= 1000);
397 }
398
399 #[test]
400 fn test_prefetch_optimizer() {
401 let optimizer = PrefetchOptimizer::new();
402
403 assert!(optimizer.should_prefetch(8, 2 * 1024 * 1024)); assert!(!optimizer.should_prefetch(2, 1024)); }
407
408 #[test]
409 fn test_cache_alignment_utilities() {
410 assert_eq!(align_to_cache_line(50), 64);
412 assert_eq!(align_to_cache_line(64), 64);
413 assert_eq!(align_to_cache_line(65), 128);
414
415 let aligned_ptr = 0x1000 as *const u8; let unaligned_ptr = 0x1001 as *const u8; assert!(is_cache_aligned(aligned_ptr));
420 assert!(!is_cache_aligned(unaligned_ptr));
421 }
422
423 #[test]
424 fn test_global_cache_optimizer() {
425 let optimizer1 = global_cache_optimizer();
426 let optimizer2 = global_cache_optimizer();
427
428 assert!(std::ptr::eq(optimizer1, optimizer2));
430
431 assert_eq!(optimizer1.cache_line_size(), 64);
433 }
434
435 #[test]
436 fn test_access_patterns() {
437 let sequential = AccessPattern::Sequential;
438 matches!(sequential, AccessPattern::Sequential);
439
440 let blocked = AccessPattern::Blocked { block_size: 128 };
441 if let AccessPattern::Blocked { block_size } = blocked {
442 assert_eq!(block_size, 128);
443 }
444
445 let tiled = AccessPattern::Tiled {
446 tile_dims: vec![64, 64],
447 };
448 if let AccessPattern::Tiled { tile_dims } = tiled {
449 assert_eq!(tile_dims, vec![64, 64]);
450 }
451 }
452
453 #[test]
454 fn test_prefetch_methods() {
455 let optimizer = PrefetchOptimizer::new();
456 let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
457
458 optimizer.prefetch_sequential(&data, 0);
460 optimizer.prefetch_strided(&data, 0, 2);
461
462 optimizer.prefetch_sequential(&data, data.len() - 1); optimizer.prefetch_strided(&data, data.len() - 1, 1); }
466}