1use std::sync::atomic::{AtomicU64, Ordering};
4use std::sync::Arc;
5
6use silicera::topology::format_bytes;
7use silicera::topology::TopologyGraph;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum WorkloadKind {
12 Memory,
14 Integer,
16 Float,
18 Branch,
20 Concurrency,
22}
23
24impl WorkloadKind {
25 pub fn label(self) -> &'static str {
27 match self {
28 WorkloadKind::Memory => "memory",
29 WorkloadKind::Integer => "integer",
30 WorkloadKind::Float => "float",
31 WorkloadKind::Branch => "branch",
32 WorkloadKind::Concurrency => "concurrency",
33 }
34 }
35}
36
37#[derive(Debug, Clone, Copy)]
39pub enum CacheTarget {
40 L1,
42 L2,
44 L3,
46 Dram,
48}
49
50impl CacheTarget {
51 pub fn class_id(self) -> &'static str {
53 match self {
54 CacheTarget::L1 => "L1",
55 CacheTarget::L2 => "L2",
56 CacheTarget::L3 => "L3",
57 CacheTarget::Dram => "DRAM",
58 }
59 }
60
61 pub fn size_bytes(self, topo: &TopologyGraph) -> u64 {
63 let l1 = topo.typical_l1d_bytes().max(32 * 1024);
64 let l2 = topo.typical_l2_bytes().max(512 * 1024);
65 let l3 = topo
66 .packages
67 .first()
68 .and_then(|p| p.domains.first())
69 .and_then(|d| d.l3_bytes())
70 .unwrap_or(32 * 1024 * 1024);
71 match self {
72 CacheTarget::L1 => l1 / 2,
73 CacheTarget::L2 => l2 / 2,
74 CacheTarget::L3 => l3 / 2,
75 CacheTarget::Dram => l3.saturating_mul(4).max(64 * 1024 * 1024),
76 }
77 }
78
79 pub fn threshold_bytes(self, topo: &TopologyGraph) -> u64 {
81 let l1 = topo.typical_l1d_bytes().max(32 * 1024);
82 let l2 = topo.typical_l2_bytes().max(512 * 1024);
83 let l3 = topo
84 .packages
85 .first()
86 .and_then(|p| p.domains.first())
87 .and_then(|d| d.l3_bytes())
88 .unwrap_or(32 * 1024 * 1024);
89 match self {
90 CacheTarget::L1 => l1,
91 CacheTarget::L2 => l2,
92 CacheTarget::L3 => l3,
93 CacheTarget::Dram => l3,
94 }
95 }
96
97 pub fn describe(self, topo: &TopologyGraph) -> String {
99 format!("{:?} (~{})", self, format_bytes(self.size_bytes(topo)))
100 }
101
102 pub fn all() -> [CacheTarget; 4] {
104 [
105 CacheTarget::L1,
106 CacheTarget::L2,
107 CacheTarget::L3,
108 CacheTarget::Dram,
109 ]
110 }
111}
112
113pub struct MemoryBench {
115 data: Vec<u8>,
117 stride: usize,
119}
120
121impl MemoryBench {
122 pub fn for_target(topo: &TopologyGraph, target: CacheTarget) -> Self {
124 let n = target.size_bytes(topo) as usize;
125 Self::with_bytes(n)
126 }
127
128 pub fn with_bytes(n: usize) -> Self {
130 let mut data = vec![0u8; n.max(64)];
131 for (i, b) in data.iter_mut().enumerate() {
132 *b = (i % 251) as u8;
133 }
134 Self { data, stride: 64 }
135 }
136
137 pub fn run(&self) -> u64 {
139 let mut sum = 0u64;
140 let mut i = 0usize;
141 while i < self.data.len() {
142 sum = sum.wrapping_add(self.data[i] as u64);
143 i += self.stride;
144 }
145 std::hint::black_box(sum)
146 }
147
148 pub fn run_prefetch_friendly(&self) -> u64 {
150 let mut sum = 0u64;
151 for chunk in self.data.chunks(64) {
152 for &b in chunk {
153 sum = sum.wrapping_add(b as u64);
154 }
155 }
156 std::hint::black_box(sum)
157 }
158}
159
160pub struct MemOpBench {
162 src: Vec<u8>,
163 dst: Vec<u8>,
164}
165
166impl MemOpBench {
167 pub fn for_target(topo: &TopologyGraph, target: CacheTarget) -> Self {
169 let n = target.size_bytes(topo) as usize;
170 Self::with_bytes(n)
171 }
172
173 pub fn with_bytes(n: usize) -> Self {
175 let n = n.max(64);
176 let mut src = vec![0u8; n];
177 for (i, b) in src.iter_mut().enumerate() {
178 *b = (i % 251) as u8;
179 }
180 let dst = vec![0u8; n];
181 Self { src, dst }
182 }
183
184 pub fn run_scan_stride(&self) -> u64 {
186 let mut sum = 0u64;
187 let mut i = 0usize;
188 while i < self.src.len() {
189 sum = sum.wrapping_add(self.src[i] as u64);
190 i += 64;
191 }
192 std::hint::black_box(sum)
193 }
194
195 pub fn run_scan_dense(&self) -> u64 {
197 let mut sum = 0u64;
198 for &b in &self.src {
199 sum = sum.wrapping_add(b as u64);
200 }
201 std::hint::black_box(sum)
202 }
203
204 pub fn run_copy(&mut self) -> u64 {
206 self.dst.copy_from_slice(&self.src);
207 std::hint::black_box(self.dst[0] as u64)
208 }
209
210 pub fn run_copy_loop(&mut self) -> u64 {
212 for i in 0..self.src.len() {
213 self.dst[i] = self.src[i];
214 }
215 std::hint::black_box(self.dst[self.dst.len() - 1] as u64)
216 }
217
218 pub fn run_copy_unrolled8(&mut self) -> u64 {
220 let n = self.src.len();
221 let mut i = 0usize;
222 while i + 8 <= n {
223 self.dst[i] = self.src[i];
224 self.dst[i + 1] = self.src[i + 1];
225 self.dst[i + 2] = self.src[i + 2];
226 self.dst[i + 3] = self.src[i + 3];
227 self.dst[i + 4] = self.src[i + 4];
228 self.dst[i + 5] = self.src[i + 5];
229 self.dst[i + 6] = self.src[i + 6];
230 self.dst[i + 7] = self.src[i + 7];
231 i += 8;
232 }
233 while i < n {
234 self.dst[i] = self.src[i];
235 i += 1;
236 }
237 std::hint::black_box(self.dst[n - 1] as u64)
238 }
239}
240
241pub struct AlignmentBench {
243 backing: Vec<u8>,
245 offset: usize,
247 len: usize,
249}
250
251impl AlignmentBench {
252 pub fn new(len: usize, offset: usize) -> Self {
254 let len = len.max(64);
255 let offset = offset % 64;
256 let mut backing = vec![0u8; len + offset + 64];
257 for (i, b) in backing.iter_mut().enumerate() {
258 *b = (i % 251) as u8;
259 }
260 Self {
261 backing,
262 offset,
263 len,
264 }
265 }
266
267 fn view(&self) -> &[u8] {
269 &self.backing[self.offset..self.offset + self.len]
270 }
271
272 pub fn run_scalar(&self) -> u64 {
274 let mut sum = 0u64;
275 for &b in self.view() {
276 sum = sum.wrapping_add(b as u64);
277 }
278 std::hint::black_box(sum)
279 }
280
281 pub fn run_chunked(&self) -> u64 {
283 let mut sum = 0u64;
284 for chunk in self.view().chunks(64) {
285 for &b in chunk {
286 sum = sum.wrapping_add(b as u64);
287 }
288 }
289 std::hint::black_box(sum)
290 }
291
292 pub fn offset(&self) -> usize {
294 self.offset
295 }
296}
297
298pub struct HostIsaReduce {
305 data: Vec<i32>,
306}
307
308impl HostIsaReduce {
309 pub fn new(n: usize) -> Self {
311 let data = (0..n as i32).map(|i| i.wrapping_mul(3).wrapping_add(1)).collect();
312 Self { data }
313 }
314
315 pub fn run_portable(&self) -> i64 {
317 let mut s = 0i64;
318 for &x in &self.data {
319 s = s.wrapping_add(x as i64);
320 }
321 std::hint::black_box(s)
322 }
323
324 pub fn run_host_isa(&self) -> i64 {
326 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
327 {
328 if is_x86_feature_detected!("avx2") {
329 return std::hint::black_box(unsafe { Self::sum_avx2(&self.data) });
331 }
332 }
333 self.run_portable()
334 }
335
336 pub fn avx2_active() -> bool {
338 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
339 {
340 return is_x86_feature_detected!("avx2");
341 }
342 #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
343 {
344 false
345 }
346 }
347
348 pub fn verify_correctness(&self) -> bool {
350 self.run_portable() == self.run_host_isa()
351 }
352
353 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
354 #[target_feature(enable = "avx2")]
355 unsafe fn sum_avx2(data: &[i32]) -> i64 {
356 #[cfg(target_arch = "x86")]
357 use std::arch::x86::*;
358 #[cfg(target_arch = "x86_64")]
359 use std::arch::x86_64::*;
360
361 let mut i = 0usize;
362 let n = data.len();
363 let mut acc = _mm256_setzero_si256();
364 while i + 8 <= n {
365 let v = unsafe { _mm256_loadu_si256(data.as_ptr().add(i) as *const __m256i) };
367 acc = _mm256_add_epi32(acc, v);
368 i += 8;
369 }
370 let mut tmp = [0i32; 8];
371 unsafe {
373 _mm256_storeu_si256(tmp.as_mut_ptr() as *mut __m256i, acc);
374 }
375 let mut s: i64 = tmp.iter().map(|&x| x as i64).sum();
376 while i < n {
377 s = s.wrapping_add(data[i] as i64);
378 i += 1;
379 }
380 s
381 }
382}
383
384pub struct HostIsaDot {
386 a: Vec<f32>,
387 b: Vec<f32>,
388}
389
390impl HostIsaDot {
391 pub fn new(n: usize) -> Self {
393 let n = n.max(8);
394 let a = (0..n).map(|i| (i as f32) * 0.001 + 1.0).collect();
395 let b = (0..n).map(|i| (i as f32) * 0.0007 + 0.5).collect();
396 Self { a, b }
397 }
398
399 pub fn run_portable(&self) -> f32 {
401 let mut s = 0.0f32;
402 for i in 0..self.a.len() {
403 s += self.a[i] * self.b[i];
404 }
405 std::hint::black_box(s)
406 }
407
408 pub fn run_host_isa(&self) -> f32 {
410 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
411 {
412 if is_x86_feature_detected!("avx") {
413 return std::hint::black_box(unsafe { Self::dot_avx(&self.a, &self.b) });
415 }
416 }
417 self.run_portable()
418 }
419
420 pub fn verify_correctness(&self, rel_tol: f32) -> bool {
422 let p = self.run_portable();
423 let h = self.run_host_isa();
424 let denom = p.abs().max(1.0);
425 (p - h).abs() / denom <= rel_tol
426 }
427
428 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
429 #[target_feature(enable = "avx")]
430 unsafe fn dot_avx(a: &[f32], b: &[f32]) -> f32 {
431 #[cfg(target_arch = "x86")]
432 use std::arch::x86::*;
433 #[cfg(target_arch = "x86_64")]
434 use std::arch::x86_64::*;
435
436 let n = a.len().min(b.len());
437 let mut i = 0usize;
438 let mut acc = _mm256_setzero_ps();
439 while i + 8 <= n {
440 let va = unsafe { _mm256_loadu_ps(a.as_ptr().add(i)) };
441 let vb = unsafe { _mm256_loadu_ps(b.as_ptr().add(i)) };
442 acc = _mm256_add_ps(acc, _mm256_mul_ps(va, vb));
443 i += 8;
444 }
445 let mut tmp = [0f32; 8];
446 unsafe {
447 _mm256_storeu_ps(tmp.as_mut_ptr(), acc);
448 }
449 let mut s: f32 = tmp.iter().sum();
450 while i < n {
451 s += a[i] * b[i];
452 i += 1;
453 }
454 s
455 }
456}
457
458#[cfg(test)]
459mod host_isa_tests {
460 use super::*;
461
462 #[test]
463 fn reduce_portable_matches_host_isa() {
464 let b = HostIsaReduce::new(1024);
465 assert!(b.verify_correctness());
466 }
467
468 #[test]
469 fn dot_portable_matches_host_isa() {
470 let b = HostIsaDot::new(2048);
471 assert!(b.verify_correctness(1e-4));
472 }
473}
474
475pub struct IntegerBench {
477 pub n: u64,
479}
480
481impl IntegerBench {
482 pub fn run_baseline(&self) -> u64 {
484 let mut x = self.n;
485 for _ in 0..10_000 {
486 x = x.wrapping_mul(1664525).wrapping_add(1013904223);
487 }
488 std::hint::black_box(x)
489 }
490
491 pub fn run_candidate(&self) -> u64 {
493 let mut x = self.n;
494 for _ in 0..10_000 {
495 x ^= x << 13;
496 x ^= x >> 7;
497 x ^= x << 17;
498 }
499 std::hint::black_box(x)
500 }
501}
502
503pub struct FloatBench {
505 data: Vec<f64>,
507}
508
509impl FloatBench {
510 pub fn new(n: usize) -> Self {
512 let data = (0..n).map(|i| (i as f64) * 0.5 + 1.0).collect();
513 Self { data }
514 }
515
516 pub fn run_baseline(&self) -> f64 {
518 let mut s = 0.0;
519 for &x in &self.data {
520 s += 1.0 / x;
521 }
522 std::hint::black_box(s)
523 }
524
525 pub fn run_candidate(&self) -> f64 {
527 fn pair(a: &[f64]) -> f64 {
528 match a.len() {
529 0 => 0.0,
530 1 => 1.0 / a[0],
531 _ => {
532 let mid = a.len() / 2;
533 pair(&a[..mid]) + pair(&a[mid..])
534 }
535 }
536 }
537 std::hint::black_box(pair(&self.data))
538 }
539}
540
541pub struct BranchBench {
543 data: Vec<u32>,
545}
546
547impl BranchBench {
548 pub fn new(n: usize) -> Self {
550 let data = (0..n as u32).map(|i| i.wrapping_mul(2654435761)).collect();
551 Self { data }
552 }
553
554 pub fn run_baseline(&self) -> u64 {
556 let mut c = 0u64;
557 for &x in &self.data {
558 if x % 3 == 0 {
559 c = c.wrapping_add(x as u64);
560 } else if x % 5 == 0 {
561 c = c.wrapping_add(1);
562 } else {
563 c = c.wrapping_sub(1);
564 }
565 }
566 std::hint::black_box(c)
567 }
568
569 pub fn run_candidate(&self) -> u64 {
571 let mut c = 0u64;
572 for &x in &self.data {
573 let m3 = ((x % 3 == 0) as u64).wrapping_mul(x as u64);
574 let m5 = ((x % 5 == 0) as u64) & (((x % 3 != 0) as u64).wrapping_neg());
575 let other = (((x % 3 != 0) && (x % 5 != 0)) as u64).wrapping_neg();
576 c = c.wrapping_add(m3).wrapping_add(m5 & 1).wrapping_add(other);
577 }
578 std::hint::black_box(c)
579 }
580}
581
582pub struct ConcurrencyBench {
584 pub iters: u64,
586 pub threads: usize,
588}
589
590impl ConcurrencyBench {
591 pub fn run_baseline(&self) -> u64 {
593 let counter = Arc::new(AtomicU64::new(0));
594 let mut handles = Vec::new();
595 for _ in 0..self.threads {
596 let c = Arc::clone(&counter);
597 let iters = self.iters;
598 handles.push(std::thread::spawn(move || {
599 for _ in 0..iters {
600 c.fetch_add(1, Ordering::Relaxed);
601 }
602 }));
603 }
604 for h in handles {
605 let _ = h.join();
606 }
607 std::hint::black_box(counter.load(Ordering::Relaxed))
608 }
609
610 pub fn run_candidate(&self) -> u64 {
612 let mut handles = Vec::new();
613 for _ in 0..self.threads {
614 let iters = self.iters;
615 handles.push(std::thread::spawn(move || {
616 let mut local = 0u64;
617 for _ in 0..iters {
618 local = local.wrapping_add(1);
619 }
620 local
621 }));
622 }
623 let mut sum = 0u64;
624 for h in handles {
625 sum = sum.wrapping_add(h.join().unwrap_or(0));
626 }
627 std::hint::black_box(sum)
628 }
629}
630