code wiki / (root) / nx_divprobe.nx

nx_divprobe.nx source

↩ module page · 95 lines · 4760 B

1// nx_divprobe.nx -- DOES nx_cc STRENGTH-REDUCE DIVISION BY A CONSTANT POWER OF TWO? 2// 3// WHY THIS PROBE EXISTS. A render-performance lane measured gs_resolve at 12.3 ns/pixel while doing 4// six divisions by 256 -- a power of two -- and concluded from an OUTLINE GREP that the x86 backend 5// has no strength-reduction pass. That is an absence claimed from a FILTERED READ, which this estate 6// does not accept as evidence: a grep that finds no function named "shr" cannot see a shift emitted 7// inline, and would report the same nothing either way. Before any compiler is edited on the strength 8// of that claim, the claim itself gets an instrument. 9// 10// THE EXPERIMENT DISCRIMINATES, which is the only reason to run it. Three loops, identical in every 11// respect except the arithmetic in the hot line: 12// A: / DP_POW2 -- divisor IS a power of two, so a strength-reducing compiler emits a shift 13// B: / DP_NOTPOW2 -- divisor is one less, NOT a power of two, so no compiler can avoid the idiv 14// C: * DP_MUL -- a multiply, the cost floor for "arithmetic that is not division" 15// 16// If A is much faster than B and close to C -> strength reduction EXISTS; the rung is REFUTED. 17// If A and B are alike and both far above C -> no strength reduction; the rung is CONFIRMED. 18// Either outcome is a result. The probe is written to be unable to flatter the answer it was built 19// hoping for: the two divisors differ by one, so nothing but the power-of-two property distinguishes 20// them, and the accumulator is PRINTED so no loop can be optimised away as dead. 21// 22// DP_ITERS is DERIVED, not picked: the clock is microsecond-resolution, and a measurement worth 23// trusting needs to exceed that by a wide margin, so the loop runs long enough that each arm takes 24// order-milliseconds. The three arms share one iteration count so their times are directly comparable. 25 26import "nx_syscalls.nx" 27import "nx_gate_verdict.nx" 28 29const DP_ITERS: i64 = 20000000 30const DP_POW2: i64 = 256 // the divisor gs_resolve and gs_blend_rect actually use (GFXA) 31const DP_NOTPOW2: i64 = 255 // one less: identical magnitude, NOT a power of two 32const DP_MUL: i64 = 3 // the non-division cost floor 33const DP_SEED: i64 = 1103515245 // an odd starting value so the accumulator does not degenerate 34 35func main() -> i64 { 36 gv_head("nx_divprobe -- is division by a constant power of two strength-reduced?" as *u8) 37 38 // ---- arm A: divide by a power of two ------------------------------------------------------ 39 var a: i64 = 0 40 var x: i64 = DP_SEED 41 let ta0: i64 = sys_clock_now_us() 42 var i: i64 = 0 43 while i < DP_ITERS { 44 a = a + x / DP_POW2 45 x = x + 1 46 i = i + 1 47 } 48 let ta1: i64 = sys_clock_now_us() 49 50 // ---- arm B: divide by a NON power of two -------------------------------------------------- 51 var b: i64 = 0 52 x = DP_SEED 53 let tb0: i64 = sys_clock_now_us() 54 i = 0 55 while i < DP_ITERS { 56 b = b + x / DP_NOTPOW2 57 x = x + 1 58 i = i + 1 59 } 60 let tb1: i64 = sys_clock_now_us() 61 62 // ---- arm C: multiply, the cost floor ------------------------------------------------------ 63 var c: i64 = 0 64 x = DP_SEED 65 let tc0: i64 = sys_clock_now_us() 66 i = 0 67 while i < DP_ITERS { 68 c = c + x * DP_MUL 69 x = x + 1 70 i = i + 1 71 } 72 let tc1: i64 = sys_clock_now_us() 73 74 let ua: i64 = ta1 - ta0 75 let ub: i64 = tb1 - tb0 76 let uc: i64 = tc1 - tc0 77 78 gv_puts(" iterations per arm = " as *u8); gv_num(DP_ITERS); gv_puts("\n" as *u8) 79 gv_puts(" A x / " as *u8); gv_num(DP_POW2); gv_puts(" (power of two) us=" as *u8); gv_num(ua); gv_puts("\n" as *u8) 80 gv_puts(" B x / " as *u8); gv_num(DP_NOTPOW2); gv_puts(" (NOT power of two) us=" as *u8); gv_num(ub); gv_puts("\n" as *u8) 81 gv_puts(" C x * " as *u8); gv_num(DP_MUL); gv_puts(" (cost floor) us=" as *u8); gv_num(uc); gv_puts("\n" as *u8) 82 83 // Accumulators are printed so that no arm can be eliminated as dead code -- an optimised-away 84 // loop would time as zero and read as an infinitely fast divide. 85 gv_puts(" accumulators (printed so no arm is dead code): " as *u8) 86 gv_num(a); gv_puts(" " as *u8); gv_num(b); gv_puts(" " as *u8); gv_num(c); gv_puts("\n\n" as *u8) 87 88 if uc > 0 { 89 gv_puts(" A/C ratio (x1000) = " as *u8); gv_num(ua * 1000 / uc); gv_puts("\n" as *u8) 90 gv_puts(" B/C ratio (x1000) = " as *u8); gv_num(ub * 1000 / uc); gv_puts("\n" as *u8) 91 } 92 gv_puts("\n READING: A close to C and far below B => strength reduction EXISTS (rung refuted).\n" as *u8) 93 gv_puts(" A close to B and both above C => NO strength reduction (rung confirmed).\n" as *u8) 94 return 0 95}