code wiki / _hdl_build / nx_hmac_alloc_ab.nx
nx_hmac_alloc_ab.nx source
↩ module page · 119 lines · 5232 B
1// nx_hmac_alloc_ab.nx -- WITHIN-SUBJECT A/B: does hoisting HMAC's scratch change anything MEASURABLE?
2//
3// ⚠THIS EXISTS BECAUSE MY PREVIOUS ATTEMPT WAS INVALID. I compared `nx_pbkdf2_sha1` (fixed) against
4// `pbkdf2_sha1` (untouched) -- but those bind DIFFERENT HASH CORES (nx_sha1.nx vs sha1.nx), so the
5// comparison changed TWO variables and its 52.7s-vs-3.6s gap is uninterpretable.
6// ★★★★★A CONTROL THAT CHANGES THE MODULE UNDER TEST IS NOT A CONTROL. I applied this correctly to the HKDF
7// triangulation hours earlier (the SHA-256 arm held fixed as an arm that MUST NOT MOVE) and then broke it.
8//
9// DESIGN: ONE process, ONE hash core (nx_sha1.nx), ONE input, TWO loops of identical count:
10// A: hmac_sha1(...) -- allocates 4 buffers per call (original)
11// B: hmac_sha1_into(...) -- allocates ZERO per call (the fix)
12// Everything else is held constant, so any difference is attributable to the single changed variable.
13// ★A WITHIN-SUBJECT DESIGN REMOVES THE CONFOUND THAT A BETWEEN-BINARY DESIGN INVITES.
14//
15// ⚠AND IT REPORTS RSS *DELTAS PER PHASE*, NOT maxresident. maxresident was shown to be useless here: it
16// read 4,800,000k for BOTH 100k and 200k iterations, with pagefault counts differing by ONE -- i.e. it was
17// reporting a FIXED RUNTIME ARENA, not consumption. ★★★★★A NUMBER IDENTICAL ACROSS TWO DIFFERENT WORKLOADS
18// IS MEASURING THE HARNESS, NOT THE WORKLOAD.
19// If the statm reader fails, this probe says UNMEASURED rather than printing a flattering 0.
20// license_tier: ORIGINAL expect_exit: 0
21import "nx_syscalls.nx"
22import "nx_hmac_sha1.nx"
23const K_MAGIC_50000: i64 = 50000
24
25func w(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
26
27func nn(v: i64) -> i64 {
28 var m: i64 = v
29 if m < 0 { w("-" as *u8); m = 0 - m }
30 let t: *u8 = sys_mmap(32)
31 var k: i64 = 0
32 if m == 0 { t[0] = 48 as u8; k = 1 }
33 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
34 let b: *u8 = sys_mmap(32)
35 var j: i64 = 0
36 while j < k { b[j] = t[k - 1 - j]; j = j + 1 }
37 sys_write(1, b, k)
38 return 0
39}
40
41// resident pages = field 2 of /proc/self/statm. Returns -1 if unreadable -- NEVER 0-on-failure.
42func rss_pages() -> i64 {
43 let lp: *i64 = sys_mmap(16) as *i64
44 lp[0] = 0
45 let b: *u8 = sys_read_file("/proc/self/statm\x00" as *u8, lp)
46 if lp[0] <= 0 { return 0 - 1 }
47 var p: i64 = 0
48 var sp: i64 = 0 - 1
49 while p < lp[0] { if b[p] == (32 as u8) { sp = p; p = lp[0] } else { p = p + 1 } }
50 if sp < 0 { return 0 - 1 }
51 var q: i64 = sp + 1
52 var v: i64 = 0
53 var any: i64 = 0
54 var done: i64 = 0
55 while done == 0 {
56 if q >= lp[0] { done = 1 }
57 else {
58 let c: i64 = b[q] as i64
59 if c >= 48 { if c <= 57 { v = v * 10 + (c - 48); any = 1; q = q + 1 } else { done = 1 } }
60 else { done = 1 }
61 }
62 }
63 if any == 0 { return 0 - 1 }
64 return v
65}
66
67func main() -> i64 {
68 let N: i64 = K_MAGIC_50000
69 w("nx_hmac_alloc_ab -- within-subject A/B, one core (nx_sha1.nx), N=" as *u8); nn(N); w(" per arm\n" as *u8)
70
71 let key: *u8 = "password\x00" as *u8
72 let msg: *u8 = sys_mmap(64)
73 var z: i64 = 0
74 while z < 20 { msg[z] = (z + 1) as u8; z = z + 1 }
75 let out: *u8 = sys_mmap(64)
76
77 let r0: i64 = rss_pages()
78
79 // ---- ARM A: allocating wrapper ----
80 let ta0: i64 = sys_now_ms()
81 var i: i64 = 0
82 while i < N { hmac_sha1(key, 8, msg, 20, out); i = i + 1 }
83 let ta1: i64 = sys_now_ms()
84 let r1: i64 = rss_pages()
85
86 // ---- ARM B: hoisted scratch, zero allocations in the loop ----
87 let scap: i64 = hmac_sha1_scratch_bytes(20)
88 let scratch: *u8 = sys_mmap(scap + 16)
89 let tb0: i64 = sys_now_ms()
90 i = 0
91 while i < N { hmac_sha1_into(scratch, scap, key, 8, msg, 20, out); i = i + 1 }
92 let tb1: i64 = sys_now_ms()
93 let r2: i64 = rss_pages()
94
95 w("\n arm A (hmac_sha1, allocating) elapsed_ms=" as *u8); nn(ta1 - ta0); w("\n" as *u8)
96 w(" arm B (hmac_sha1_into, hoisted) elapsed_ms=" as *u8); nn(tb1 - tb0); w("\n" as *u8)
97
98 if r0 < 0 {
99 w("\n rss reader UNAVAILABLE -- reporting TIME ONLY, no memory verdict.\n" as *u8)
100 w("nx_hmac_alloc_ab: verdict=UNMEASURED-MEMORY (time-only)\n" as *u8)
101 return 0
102 }
103 w(" rss growth arm A = " as *u8); nn((r1 - r0) * 4); w(" KB\n" as *u8)
104 w(" rss growth arm B = " as *u8); nn((r2 - r1) * 4); w(" KB\n" as *u8)
105
106 // The claim under test is narrow and falsifiable: arm B must not grow the resident set the way arm A
107 // does. It says nothing about correctness -- both arms compute the same MAC by construction.
108 let ga: i64 = (r1 - r0) * 4
109 let gb: i64 = (r2 - r1) * 4
110 w("\n ref=within-subject-AB probe=nx_hmac_alloc_ab\n" as *u8)
111 if gb * 4 < ga {
112 w("nx_hmac_alloc_ab: armA_kb=" as *u8); nn(ga); w(" armB_kb=" as *u8); nn(gb)
113 w(" verdict=GREEN -- hoisting measurably reduces resident growth\n" as *u8)
114 return 0
115 }
116 w("nx_hmac_alloc_ab: armA_kb=" as *u8); nn(ga); w(" armB_kb=" as *u8); nn(gb)
117 w(" verdict=RED -- NO measurable reduction; the allocation removal does not show up here\n" as *u8)
118 return 1
119}