6a6b589ba0
Adds el_black_box (inline asm, +r constraint, memory clobber) and runtime/elbench.el: a growth-curve classifier that gates time AND allocation-count AND allocation-bytes, failing if any exceeds its declared curve. Refusal is a first-class verdict. The classifier REFUSES rather than classifying when the largest measurement is below the floor, or when a series is hard-flat across an 8x input range -- the shape produced when the optimiser deletes the work. Reporting O(1) there would be a confident answer with nothing behind it. Disagreeing ratios report INDETERMINATE rather than a guess. Deviation from DESIGN.md 6.2, stated in the source: uses consecutive ratios on a mandated geometric sweep rather than least-squares over candidate curves. Ratios are directly interpretable on a doubling sweep and need no floating point; the cost is weaker O(n) vs O(n log n) separation, reported as an ambiguous band rather than guessed. Documents the counter scope limit: engram_*.c and libcurl malloc are NOT tracked, so a flat curve over engram/HTTP-dominated work is not evidence of anything. 13 tests prove the classifier against real measured series from fitprobe.el -- including that an accumulator's allocation COUNT is linear while its bytes are quadratic, and that el #132's pure-CPU shape reads FLAT on both allocation signals and is caught only by time.
132 lines
5.0 KiB
EmacsLisp
132 lines
5.0 KiB
EmacsLisp
import "../../runtime/eltest.el"
|
|
import "../../runtime/elbench.el"
|
|
|
|
// test_elbench.el — proves the growth-curve classifier against KNOWN curves.
|
|
//
|
|
// Every series below is real measured data from lang/tests/bench/fitprobe.el
|
|
// on a geometric sweep n = 200/400/800/1600. The classifier must be provable
|
|
// without depending on a live defect existing, which is the whole point of
|
|
// keeping controlled specimens.
|
|
|
|
fn _s4(a: Int, b: Int, c: Int, d: Int) -> [Int] {
|
|
let l: [Int] = native_list_empty()
|
|
let l = native_list_append(l, a)
|
|
let l = native_list_append(l, b)
|
|
let l = native_list_append(l, c)
|
|
let l = native_list_append(l, d)
|
|
return l
|
|
}
|
|
|
|
test "classifies a linear allocation series as O(n)" {
|
|
// fitprobe `linear`, allocation count
|
|
let v = _s4(208, 409, 810, 1611)
|
|
let c: Int = elb_measured_curve(v, 10)
|
|
assert c == 2, "linear allocs should classify O(n)"
|
|
}
|
|
|
|
test "classifies a linear byte series as O(n)" {
|
|
// fitprobe `linear`, allocation bytes
|
|
let v = _s4(4786, 9682, 19474, 39658)
|
|
let c: Int = elb_measured_curve(v, 10)
|
|
assert c == 2, "linear bytes should classify O(n)"
|
|
}
|
|
|
|
test "classifies a quadratic byte series as O(n^2)" {
|
|
// fitprobe `accum`, allocation bytes -- the accumulator-rebuild shape
|
|
let v = _s4(20300, 80600, 321200, 1282400)
|
|
let c: Int = elb_measured_curve(v, 10)
|
|
assert c == 4, "accum bytes should classify O(n^2)"
|
|
}
|
|
|
|
test "accumulator count is linear -- proves count alone misses it" {
|
|
// Same run as above. The COUNT is exactly linear while bytes are
|
|
// quadratic. A count-only gate passes this defect clean.
|
|
let v = _s4(200, 400, 800, 1600)
|
|
let c: Int = elb_measured_curve(v, 10)
|
|
assert c == 2, "accum count classifies O(n)"
|
|
let g: Int = elb_gate(v, 2, 10)
|
|
assert g == 0, "count-only gate PASSES the quadratic"
|
|
}
|
|
|
|
test "classifies a quadratic time series as O(n^2)" {
|
|
// fitprobe `compute` -- el #132's shape: n scans over n characters
|
|
let v = _s4(67, 205, 818, 3268)
|
|
let c: Int = elb_measured_curve(v, 10)
|
|
assert c == 4, "compute time should classify O(n^2)"
|
|
}
|
|
|
|
test "REFUSES an all-zero series instead of calling it O(1)" {
|
|
// fitprobe `compute` allocation count. Pure CPU, allocates nothing.
|
|
// Reporting O(1) here would be a confident answer with nothing behind it.
|
|
let v = _s4(0, 0, 0, 0)
|
|
let g: Int = elb_gate(v, 2, 10)
|
|
assert g == 3, "all-zero series must be REFUSED"
|
|
// NOTE: bind before comparing. `call(...) == <non-literal>` lowers to str_eq()
|
|
// on integers and segfaults -- see the elc == inference bug reported with
|
|
// this change. `let x = call(); x == y` is the safe form.
|
|
let got: Int = elb_measured_curve(v, 10)
|
|
assert got < 0, "unclassifiable returns -1"
|
|
}
|
|
|
|
test "REFUSES an implausibly flat series" {
|
|
// The shape produced when clang closes a loop to a multiply: a real
|
|
// answer, no work done, no movement across an 8x input range.
|
|
let v = _s4(1000, 1001, 1002, 1003)
|
|
let g: Int = elb_gate(v, 2, 10)
|
|
assert g == 3, "hard-flat series must be REFUSED"
|
|
}
|
|
|
|
test "gate FAILS a quadratic declared as linear" {
|
|
let v = _s4(20300, 80600, 321200, 1282400)
|
|
let g: Int = elb_gate(v, 2, 10)
|
|
assert g == 1, "O(n^2) measured vs O(n) declared must FAIL"
|
|
}
|
|
|
|
test "gate PASSES a linear series declared as linear" {
|
|
let v = _s4(208, 409, 810, 1611)
|
|
let g: Int = elb_gate(v, 2, 10)
|
|
assert g == 0, "O(n) measured vs O(n) declared must PASS"
|
|
}
|
|
|
|
test "gate reports BETTER when measured beats the declared bound" {
|
|
let v = _s4(208, 409, 810, 1611)
|
|
let g: Int = elb_gate(v, 4, 10)
|
|
assert g == 4, "O(n) measured vs O(n^2) declared is BETTER"
|
|
}
|
|
|
|
test "gate reports INDETERMINATE on disagreeing ratios" {
|
|
// fitprobe `linear` WALL TIME at these sizes: 26/19/43/78 microseconds.
|
|
// Ratios 0.73, 2.26, 1.81 disagree well past the noise threshold. The
|
|
// honest answer is "cannot tell", not a classification -- this is exactly
|
|
// why benchmarks need auto-scaled iteration counts rather than one shot.
|
|
let v = _s4(26, 19, 43, 78)
|
|
let g: Int = elb_gate(v, 2, 10)
|
|
assert g == 2, "disagreeing ratios must be INDETERMINATE"
|
|
}
|
|
|
|
test "black_box is a real barrier and returns its input" {
|
|
let bb: Int = el_black_box(42)
|
|
assert bb == 42, "black_box is value-preserving"
|
|
let s: Int = 0
|
|
let i: Int = 0
|
|
while i < 100 {
|
|
// Bind the call before using it in arithmetic: `x + call(...)`
|
|
// lowers to el_str_concat() on integers. Same inference defect
|
|
// as `call(...) == y` lowering to str_eq().
|
|
let bx: Int = el_black_box(1)
|
|
let s = s + bx
|
|
let i = i + 1
|
|
}
|
|
assert s == 100, "black_box does not disturb the computation"
|
|
}
|
|
|
|
test "curve names round-trip" {
|
|
let k1: Int = elb_curve_from_name("O(n)")
|
|
assert k1 == 2, "O(n) parses"
|
|
let k2: Int = elb_curve_from_name("O(n^2)")
|
|
assert k2 == 4, "O(n^2) parses"
|
|
assert str_eq(elb_curve_name(4), "O(n^2)"), "O(n^2) renders"
|
|
let unk: Int = elb_curve_from_name("O(nonsense)")
|
|
assert unk < 0, "unknown curve is -1"
|
|
}
|