nx_membound.nx
buildroot/runtime/nx_membound.nx
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
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
structs
| none |
consts
| 35 | const MB_W64: i64 = 8 |
| 36 | const MB_BIG_PX: i64 = 8294400 // 3840*2160: the real 4K working set |
| 41 | const MB_REPS: i64 = 512 |
| 42 | const MB_SMALL_PX: i64 = 16200 // = MB_BIG_PX / MB_REPS; 5 arrays * 8 B * 16200 = 633 KiB, cache-resident |
| 43 | const MB_TOTAL: i64 = 8294400 // identical element-updates in both arms, now EXACTLY so |
| 44 | const MB_FXA: i64 = 256 // the renderer's own fixed-point shift, so the arithmetic matches |
functions
| 48 | func mb_pass(acc: *i64, trans: *i64, fb: *i64, n: i64, reps: i64) -> i64 called by 1: main |
| 70 | func main() -> i64 |