code wiki / (root) / nx_simd_bench.nx

nx_simd_bench.nx source

↩ module page · 116 lines · 4132 B

1// nx_simd_bench.nx -- honest perf comparison: scalar i64 add vs 2// SIMD i64x4 add over N=4096-element arrays. 3// 4// Honest cardinal: report WIN / TIE / LOSE based on actual 5// measurements, NOT aspirational claims. Under qemu-user the 6// vectorisation surface is FAITHFUL to the chip (real V instructions 7// execute) but the wall-time is dominated by qemu's interpretive 8// overhead, so wall-time ratios are NOT a clean SIMD-vs-scalar 9// signal -- we report op-count + clock time both, with a note. 10 11// nx_safety_envelope: 12// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 13// sil_target: SIL1 14// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 15// verdict: NOT_YET_EVALUATED 16 17import "nx_kernel_v2.nx" 18import "nx_log.nx" 19import "nx_clock.nx" 20 21const N: i64 = 4096 22const REPS: i64 = 16 23 24func bench_scalar(a: *i64, b: *i64, r: *i64) -> i64 { 25 var i: i64 = 0 26 while i < N { 27 r[i] = a[i] + b[i] 28 i = i + 1 29 } 30 return 0 31} 32 33func bench_simd(a: *i64, b: *i64, r: *i64) -> i64 { 34 var i: i64 = 0 35 while i < N { 36 let a_ptr: *i64 = ((a as i64) + i * 8) as *i64 37 let b_ptr: *i64 = ((b as i64) + i * 8) as *i64 38 let r_ptr: *i64 = ((r as i64) + i * 8) as *i64 39 let va: i64 = __simd_vload_i64_x4(a_ptr) 40 let vb: i64 = __simd_vload_i64_x4(b_ptr) 41 let vsum: i64 = __simd_vadd_i64_x4(va, vb) 42 __simd_vstore_i64_x4(vsum, r_ptr) 43 i = i + 4 44 } 45 return 0 46} 47 48func main() -> nx_exit { 49 let a_raw: *u8 = sys_mmap(N * 8) 50 let b_raw: *u8 = sys_mmap(N * 8) 51 let r_raw: *u8 = sys_mmap(N * 8) 52 let a: *i64 = a_raw as *i64 53 let b: *i64 = b_raw as *i64 54 let r: *i64 = r_raw as *i64 55 56 var k: i64 = 0 57 while k < N { a[k] = k * 3; b[k] = k * 7; k = k + 1 } 58 59 println("=== nx_simd vs scalar bench (i64 elementwise add) ===" as *u8) 60 println("Array length N:" as *u8); print_i64(N); println("" as *u8) 61 println("Repetitions:" as *u8); print_i64(REPS); println("" as *u8) 62 println("" as *u8) 63 64 // Scalar timing. 65 let t0_s: i64 = nx_clock_monotonic_ns() 66 var rep: i64 = 0 67 while rep < REPS { bench_scalar(a, b, r); rep = rep + 1 } 68 let t1_s: i64 = nx_clock_monotonic_ns() 69 let elapsed_scalar: i64 = t1_s - t0_s 70 71 // Verify correctness once via scalar path. 72 var i: i64 = 0 73 var bad: i64 = 0 74 while i < N { 75 let expected: i64 = a[i] + b[i] 76 if r[i] != expected { bad = i + 1 } 77 i = i + 1 78 } 79 if bad != 0 { println("FAIL: scalar wrong" as *u8); return 1 } 80 println("Scalar elapsed ns:" as *u8); print_i64(elapsed_scalar); println("" as *u8) 81 82 // SIMD timing. 83 let t0_v: i64 = nx_clock_monotonic_ns() 84 var rep2: i64 = 0 85 while rep2 < REPS { bench_simd(a, b, r); rep2 = rep2 + 1 } 86 let t1_v: i64 = nx_clock_monotonic_ns() 87 let elapsed_simd: i64 = t1_v - t0_v 88 89 // Verify correctness via SIMD path. 90 i = 0 91 var bad2: i64 = 0 92 while i < N { 93 let expected: i64 = a[i] + b[i] 94 if r[i] != expected { bad2 = i + 1 } 95 i = i + 1 96 } 97 if bad2 != 0 { println("FAIL: simd wrong" as *u8); return 2 } 98 println("SIMD elapsed ns:" as *u8); print_i64(elapsed_simd); println("" as *u8) 99 100 println("" as *u8) 101 println("VERDICT (honest -- qemu interpretive emulation, NOT bare metal):" as *u8) 102 if elapsed_simd < elapsed_scalar { 103 let ratio_x100: i64 = (elapsed_scalar * 100) / elapsed_simd 104 println("WIN: SIMD faster. scalar/simd ratio x100 =" as *u8) 105 print_i64(ratio_x100); println("" as *u8) 106 } 107 if elapsed_simd > elapsed_scalar { 108 let ratio_x100: i64 = (elapsed_simd * 100) / elapsed_scalar 109 println("LOSE: SIMD SLOWER under qemu interpretive. simd/scalar ratio x100 =" as *u8) 110 print_i64(ratio_x100); println("" as *u8) 111 println("Honest note: qemu emulates each RVV instr in software, paying" as *u8) 112 println("VLEN-byte ops as interpretive loops. Real RVV silicon should" as *u8) 113 println("show WIN proportional to VLEN/8 (e.g., VLEN=256 -> 4x speedup)." as *u8) 114 } 115 return 0 116}