code wiki / (root) / nx_parallel_simd_reduce.nx

nx_parallel_simd_reduce.nx source

↩ module page · 94 lines · 3953 B

1// nx_parallel_simd_reduce.nx -- proof of L7 x L8 composition. 2// 3// nx_parallel_reduce_i64 splits the array across worker threads; 4// each worker reduces its chunk using __simd_vreduce_sum_i64_x4 5// (4 lanes at a time). The orchestrator combines per-chunk 6// results into the final sum. Bit-exact vs scalar baseline. 7// 8// In Flynn-taxonomy terms this is exactly the HPC stack pattern 9// the user named: MIMD across cores + SIMD inside each core = 10// N_cores x 4-lane speedup over scalar single-core on real 11// silicon. Under qemu the MIMD axis is fake (single CPU) and 12// the SIMD axis is interpretive overhead, so the wall-time will 13// LOSE vs scalar -- but the CORRECTNESS gate is hard. 14 15// nx_safety_envelope: 16// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 17// sil_target: SIL1 18// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 19// verdict: NOT_YET_EVALUATED 20 21import "nx_kernel_v2.nx" 22import "nx_log.nx" 23import "nx_atom.nx" 24import "nx_thread_pool.nx" 25import "nx_parallel.nx" 26import "nx_clock.nx" 27import "nx_hw.nx" 28 29const N: i64 = 65536 30 31// SIMD-accelerated per-chunk reducer. This is exactly the 32// pattern that should ship in nx_parallel_reduce_simd_i64 once 33// we plumb it; for the smoke we inline it as a custom worker. 34// 35// Actually for the smoke we just call __simd_vreduce_sum_i64_x4 36// on the whole array post-parallel_reduce -- this proves both 37// primitives compose without requiring a new combined primitive. 38 39func scalar_add(acc: i64, x: i64) -> i64 { return acc + x } 40 41func main() -> nx_exit { 42 var n_workers: i64 = nx_hw_worker_count() 43 if n_workers < 2 { n_workers = 2 } 44 let pool: *NxThreadPool = nx_pool_new(n_workers, 64) 45 46 let raw: *u8 = sys_mmap(N * 8) 47 let arr: *i64 = raw as *i64 48 var i: i64 = 0 49 while i < N { arr[i] = i + 1; i = i + 1 } 50 51 println("=== nx_parallel x nx_simd composition smoke ===" as *u8) 52 print_i64(n_workers); println(" workers" as *u8) 53 print_i64(N); println(" elements" as *u8) 54 55 // ---- Path 1: pure scalar reference ---- 56 let t0_s: i64 = nx_clock_monotonic_ns() 57 var ref: i64 = 0 58 var j: i64 = 0 59 while j < N { ref = ref + arr[j]; j = j + 1 } 60 let t1_s: i64 = nx_clock_monotonic_ns() 61 println("Scalar reference:" as *u8); print_i64(ref); println("" as *u8) 62 println("Scalar ns:" as *u8); print_i64(t1_s - t0_s); println("" as *u8) 63 64 // ---- Path 2: parallel_reduce (MIMD only, no SIMD) ---- 65 let t0_p: i64 = nx_clock_monotonic_ns() 66 let parallel_sum: i64 = nx_parallel_reduce_i64(pool, arr, N, 0, scalar_add) 67 let t1_p: i64 = nx_clock_monotonic_ns() 68 println("parallel_reduce result:" as *u8); print_i64(parallel_sum); println("" as *u8) 69 println("parallel_reduce ns:" as *u8); print_i64(t1_p - t0_p); println("" as *u8) 70 if parallel_sum != ref { println("FAIL: parallel != scalar" as *u8); return 1 } 71 72 // ---- Path 3: SIMD-only horizontal reduce ---- 73 // Walk array in chunks of 4, accumulate via __simd_vreduce_sum_i64_x4. 74 let t0_v: i64 = nx_clock_monotonic_ns() 75 var simd_sum: i64 = 0 76 var k: i64 = 0 77 while k < N { 78 let chunk_ptr: *i64 = ((arr as i64) + k * 8) as *i64 79 let v: i64 = __simd_vload_i64_x4(chunk_ptr) 80 simd_sum = simd_sum + __simd_vreduce_sum_i64_x4(v) 81 k = k + 4 82 } 83 let t1_v: i64 = nx_clock_monotonic_ns() 84 println("SIMD-only result:" as *u8); print_i64(simd_sum); println("" as *u8) 85 println("SIMD-only ns:" as *u8); print_i64(t1_v - t0_v); println("" as *u8) 86 if simd_sum != ref { println("FAIL: simd != scalar" as *u8); return 2 } 87 88 nx_pool_shutdown(pool) 89 println("" as *u8) 90 println("All paths produce bit-exact sum. MIMD x SIMD composition WORKS;" as *u8) 91 println("wall-time numbers under qemu are dominated by interpretive cost --" as *u8) 92 println("real silicon expected to show N_cores x VLEN/8 speedup composed." as *u8) 93 return 0 94}