Probes the ceiling and incremental strategy for compiling hot fns to native C, the only lever that moves the ~10.8x Janet-VM floor the localization spike found. Native-C mandelbrot (Janet native module) runs ~10-12ms — faster than JVM Clojure (14.2ms) and ~18-22x faster than jolt's 219ms. The boundary cost is asymmetric: a bytecode loop calling a C hot-fn 40k times is nearly free (~11ms), but a C fn calling back into bytecode via janet_call costs ~3.5us/call (~152ms, no win). So the strategy is leaf-first / whole-hot-cluster compilation, crossing only at cold edges. A plain cc-built .so (no jpm) loads at runtime via require at full speed, so the native tier fits jolt's dynamic compile model. Adds the spike artifacts under spike/native/ and the writeup. Next step is jolt-ihdp (IR->C for the numeric subset). No source changes. Co-authored-by: Yogthos <yogthos@gmail.com>
53 lines
1.7 KiB
Text
53 lines
1.7 KiB
Text
# Benchmark the native-C mandelbrot vs the spike's other legs (jolt-5vsp lever 1).
|
|
# janet spike/native/bench-native.janet 200
|
|
(import ./build/mandel :as mandel)
|
|
|
|
(defn bench [label f n]
|
|
(repeat 2 (f (div n 2)))
|
|
(def times @[])
|
|
(var last-r 0)
|
|
(repeat 3
|
|
(def t0 (os/clock))
|
|
(set last-r (f n))
|
|
(array/push times (* 1000.0 (- (os/clock) t0))))
|
|
(printf "%-28s n %d result %d mean %.2f ms"
|
|
label n last-r (/ (sum times) 3)))
|
|
|
|
# Leg A: whole run in native C (pure native-codegen ceiling).
|
|
(defn run-pure-c [n] (mandel/run-c n))
|
|
|
|
# Leg B: Janet `while` loop, but count-point is a native C cfunction called n^2
|
|
# times — measures the Janet->C boundary-crossing cost (the incremental hybrid).
|
|
(defn run-boundary [n]
|
|
(def cap 200)
|
|
(def nd (* 1.0 n))
|
|
(var acc 0)
|
|
(var y 0)
|
|
(while (< y n)
|
|
(def ci (- (/ (* 2.0 y) nd) 1.0))
|
|
(var x 0)
|
|
(var a 0)
|
|
(while (< x n)
|
|
(def cr (- (/ (* 2.0 x) nd) 1.5))
|
|
(set a (+ a (mandel/count-point-c cr ci cap)))
|
|
(++ x))
|
|
(set acc (+ acc a))
|
|
(++ y))
|
|
acc)
|
|
|
|
# Leg C: C run loop calling a Janet bytecode count-point back via janet_call n^2
|
|
# times — the reverse crossing (hot C fn -> cold bytecode helper).
|
|
(defn count-point-janet [cr ci cap]
|
|
(var i 0) (var zr 0.0) (var zi 0.0)
|
|
(while (and (< i cap) (<= (+ (* zr zr) (* zi zi)) 4.0))
|
|
(def nzr (+ (- (* zr zr) (* zi zi)) cr))
|
|
(def nzi (+ (* 2.0 (* zr zi)) ci))
|
|
(set zr nzr) (set zi nzi) (++ i))
|
|
i)
|
|
(defn run-callback [n] (mandel/run-callback n count-point-janet))
|
|
|
|
(defn main [& args]
|
|
(def n (if (> (length args) 1) (scan-number (get args 1)) 200))
|
|
(bench "native-C whole run" run-pure-c n)
|
|
(bench "Janet loop -> C count-point" run-boundary n)
|
|
(bench "C loop -> janet_call back" run-callback n))
|