nx_perf_bench.nx source
↩ module page · 204 lines · 6105 B
1// nx_perf_bench.nx -- substrate-native performance benchmark primitive.
2//
3// Closes long-pending task #15. Substrate primitive that:
4// 1. Times an operation N times via nx_clock_monotonic_ns
5// 2. Computes min / median / mean / p99 over the runs
6// 3. Compares against a declared baseline (incumbent_label + ns)
7// 4. Returns a sealed verdict: BEATS / TIES / LOSES / NO_BASELINE
8//
9// Used by the TheoremCard's `perf_vs_*_status` fields per
10// S_CLASS_MATH_DOCTRINE axis 3.
11//
12// genealogy_id: knuth_taocp_v1 + benchmarking_discipline
13// lineage_id: performance_measurement + sealed_verdict
14// axioms: NX_AX_ORD_LEAST_UPPER_BOUND (timing values total-ordered)
15
16// nx_safety_envelope:
17// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
18// sil_target: SIL1
19// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
20// verdict: NOT_YET_EVALUATED
21
22import "syscalls.nx"
23import "nx_axioms.nx"
24import "nx_clock.nx"
25
26// ===== sealed verdicts (mirror nx_theorem_card's perf statuses) ========
27
28const NX_PERF_VERDICT_NO_BASELINE: i64 = 0
29const NX_PERF_VERDICT_WINS: i64 = 1 // ours <= baseline
30const NX_PERF_VERDICT_LOSES: i64 = 2 // ours > baseline by >= 10%
31const NX_PERF_VERDICT_TIES: i64 = 3 // within 10%
32
33// ===== sample buffer + descriptors ====================================
34
35struct PerfRun {
36 label: *u8, // theorem / op label
37 n_iterations: i64,
38 samples_ns: *i64, // length n_iterations
39 min_ns: i64,
40 max_ns: i64,
41 median_ns: i64,
42 mean_ns: i64,
43 p99_ns: i64,
44 baseline_label: *u8, // incumbent name
45 baseline_ns: i64, // 0 if no baseline
46 verdict: i64,
47}
48
49const NX_PERF_RUN_BYTES: i64 = 88
50
51func nx_perf_run_alloc(label: *u8, n_iter: i64) -> *PerfRun {
52 let raw: *u8 = sys_mmap(NX_PERF_RUN_BYTES)
53 let r: *PerfRun = raw as *PerfRun
54 r.label = label
55 r.n_iterations = n_iter
56 r.samples_ns = (sys_mmap(n_iter * 8)) as *i64
57 r.min_ns = 0
58 r.max_ns = 0
59 r.median_ns = 0
60 r.mean_ns = 0
61 r.p99_ns = 0
62 r.baseline_label = "" as *u8
63 r.baseline_ns = 0
64 r.verdict = NX_PERF_VERDICT_NO_BASELINE
65 return r
66}
67
68// Record one sample.
69func nx_perf_record_sample(r: *PerfRun, idx: i64, ns: i64) -> i64 {
70 if idx < 0 { return -1 }
71 if idx >= r.n_iterations { return -1 }
72 r.samples_ns[idx] = ns
73 return 0
74}
75
76// Insertion sort over the samples (small n_iter -- adequate; queued
77// nx_qsort upgrade for larger N).
78func nx_perf_sort_samples(r: *PerfRun) -> i64 {
79 var i: i64 = 1
80 while i < r.n_iterations {
81 let key: i64 = r.samples_ns[i]
82 var j: i64 = i - 1
83 while j >= 0 {
84 if r.samples_ns[j] > key {
85 r.samples_ns[j + 1] = r.samples_ns[j]
86 j = j - 1
87 }
88 if r.samples_ns[j] <= key { j = -1 }
89 }
90 r.samples_ns[j + 1] = key
91 i = i + 1
92 }
93 return 0
94}
95
96// Compute summary stats over collected samples.
97func nx_perf_summarize(r: *PerfRun) -> i64 {
98 if r.n_iterations <= 0 { return 0 }
99 nx_perf_sort_samples(r)
100 r.min_ns = r.samples_ns[0]
101 r.max_ns = r.samples_ns[r.n_iterations - 1]
102 r.median_ns = r.samples_ns[r.n_iterations / 2]
103 let p99_idx: i64 = (r.n_iterations * 99) / 100
104 var pi: i64 = p99_idx
105 if pi >= r.n_iterations { pi = r.n_iterations - 1 }
106 r.p99_ns = r.samples_ns[pi]
107 var sum: i64 = 0
108 var i: i64 = 0
109 while i < r.n_iterations {
110 sum = sum + r.samples_ns[i]
111 i = i + 1
112 }
113 r.mean_ns = sum / r.n_iterations
114 return 0
115}
116
117// Declare baseline + compute verdict.
118func nx_perf_set_baseline(r: *PerfRun, baseline_label: *u8, baseline_ns: i64) -> i64 {
119 r.baseline_label = baseline_label
120 r.baseline_ns = baseline_ns
121 if baseline_ns <= 0 {
122 r.verdict = NX_PERF_VERDICT_NO_BASELINE
123 return 0
124 }
125 let ours: i64 = r.median_ns
126 // 10% tolerance.
127 let tol: i64 = baseline_ns / 10
128 if ours <= baseline_ns { r.verdict = NX_PERF_VERDICT_WINS }
129 if ours > baseline_ns {
130 if ours - baseline_ns <= tol { r.verdict = NX_PERF_VERDICT_TIES }
131 if ours - baseline_ns > tol { r.verdict = NX_PERF_VERDICT_LOSES }
132 }
133 return 0
134}
135
136// ===== JSON-line emit =================================================
137
138func pb_putc(fd: i64, c: i64) -> i64 {
139 let buf: *u8 = sys_mmap(1)
140 buf[0] = c & 0xFF
141 sys_write(fd, buf, 1)
142 return 0
143}
144
145func pb_str(fd: i64, s: *u8, n: i64) -> i64 {
146 sys_write(fd, s, n)
147 return 0
148}
149
150func pb_strz(fd: i64, s: *u8) -> i64 {
151 var i: i64 = 0
152 while s[i] != 0 { i = i + 1 }
153 sys_write(fd, s, i)
154 return i
155}
156
157func pb_i64(fd: i64, n: i64) -> i64 {
158 if n < 0 {
159 pb_putc(fd, 45)
160 return pb_i64(fd, -n)
161 }
162 if n == 0 {
163 pb_putc(fd, 48)
164 return 0
165 }
166 let digits: *u8 = sys_mmap(32)
167 var d: i64 = 0
168 var v: i64 = n
169 while v > 0 {
170 digits[d] = (v % 10) + 48
171 v = v / 10
172 d = d + 1
173 }
174 while d > 0 {
175 d = d - 1
176 pb_putc(fd, digits[d])
177 }
178 return 0
179}
180
181func nx_perf_emit_card(fd: i64, r: *PerfRun) -> i64 {
182 pb_str(fd, "{\"phase\":\"PERF_BENCH\",\"label\":\"", 31)
183 pb_strz(fd, r.label)
184 pb_str(fd, "\",\"n_iter\":", 11)
185 pb_i64(fd, r.n_iterations)
186 pb_str(fd, ",\"min_ns\":", 10)
187 pb_i64(fd, r.min_ns)
188 pb_str(fd, ",\"median_ns\":", 13)
189 pb_i64(fd, r.median_ns)
190 pb_str(fd, ",\"mean_ns\":", 11)
191 pb_i64(fd, r.mean_ns)
192 pb_str(fd, ",\"max_ns\":", 10)
193 pb_i64(fd, r.max_ns)
194 pb_str(fd, ",\"p99_ns\":", 10)
195 pb_i64(fd, r.p99_ns)
196 pb_str(fd, ",\"baseline\":\"", 13)
197 pb_strz(fd, r.baseline_label)
198 pb_str(fd, "\",\"baseline_ns\":", 16)
199 pb_i64(fd, r.baseline_ns)
200 pb_str(fd, ",\"verdict\":", 11)
201 pb_i64(fd, r.verdict)
202 pb_str(fd, "}\n", 2)
203 return 0
204}