code wiki / (root) / nx_membound.nx

nx_membound.nx

buildroot/runtime/nx_membound.nx

6357 B116 linesdepth 3pulls 3 transitivereach 0 importersview sourcekind tool
docsdependenciesstructsconstsfunctions

about

nx_membound.nx -- IS THE SOVEREIGN RASTERIZER MEMORY-BOUND AT HIGH RESOLUTION? WHY THIS PROBE EXISTS. Two measurements pointed away from arithmetic and nobody had tested the alternative: (1) gs_resolve costs ~12.3 ns/pixel at 4K while doing ONLY shifts, multiplies and one store -- nx_divprobe proved division by a constant power of two is already strength-reduced (7.1x faster than a non-power-of-two divisor), so resolve is NOT divide-bound as was claimed. At 40 bytes touched per pixel, 12.3 ns/px is ~3.3 GB/s: a MEMORY number, not an ALU number. (2) A forward-differencing rewrite of the blend loop REMOVED six multiplies per pixel and made the frame SLOWER. An arithmetic saving that does not show up is the signature of a pipeline that is waiting on something other than the ALU. The estate is about to be told to TILE the blend for cache locality on the strength of that reading. A recommendation is not a measurement, so this probe tests the premise first: if the renderer is memory-bound, then confining the working set to cache must be markedly faster for the SAME arithmetic and the SAME element count -- and if it is not, tiling will not pay and the recommendation must be withdrawn before anyone builds it. THE EXPERIMENT DISCRIMINATES. Both arms do IDENTICAL total work: MB_TOTAL element-updates, the same expression, the same accumulate-and-store shape as gs_resolve. They differ in ONE property: BIG -- strides once through a buffer far larger than any cache, touching each element once. SMALL -- loops many times over a buffer sized to sit in cache, touching each element many times. Same instructions, same count; only the working set differs. A large BIG/SMALL ratio is the definition of memory-bound. SIZES ARE DERIVED, NOT PICKED. MB_BIG_PX is the 4K pixel count the beauty tier actually renders (3840*2160), so the BIG arm reproduces the real working set rather than a synthetic one. MB_SMALL_PX is chosen small enough that its four arrays (4 * 8 B * MB_SMALL_PX = 512 KiB) fit comfortably inside a typical L2, which is the whole point of the comparison. MB_TOTAL is the BIG arm's element count so the two arms are directly comparable without scaling.

dependencies 2 imports · 0 importers

nx_syscalls.nx nx_gate_verdict.nx nx_membound.nx

imports: nx_syscalls.nxnx_gate_verdict.nx

imported by: nobody (leaf or entry point)

call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown

main gv_head gv_puts sys_write sys_mmap nxa_die sys_write ↻ sys_exit nxa_lock_take nxa_lock_addr sys_write ↻ nxa_lock_give nxa_lock_addr ↻ nxa_report_overrun sys_write ↻ nxa_dump_printable sys_write ↻ nxa_dump_sizes sys_write ↻ mb_pass sys_clock_now_us sys_now_us sys_mmap ↻ sys_clock_gettime_mono gv_puts ↻ gv_num sys_mmap ↻ sys_write ↻ sys_munmap gv_check gv_puts ↻ gv_ctr sys_mmap ↻

structs

none

consts

35const MB_W64: i64 = 8
36const MB_BIG_PX: i64 = 8294400 // 3840*2160: the real 4K working set
41const MB_REPS: i64 = 512
42const MB_SMALL_PX: i64 = 16200 // = MB_BIG_PX / MB_REPS; 5 arrays * 8 B * 16200 = 633 KiB, cache-resident
43const MB_TOTAL: i64 = 8294400 // identical element-updates in both arms, now EXACTLY so
44const MB_FXA: i64 = 256 // the renderer's own fixed-point shift, so the arithmetic matches

functions

48func mb_pass(acc: *i64, trans: *i64, fb: *i64, n: i64, reps: i64) -> i64
called by 1: main
70func main() -> i64