perf: hint-driven keyword-lookup guard elimination (^:struct)
A constant-keyword lookup (:k m) currently emits a guarded form, (if (get m :jolt/type) (core-get m k) (get m k)), to tell a plain struct (raw get is correct) from a phm/sorted/transient (needs core-get). On a struct that guard is a second get, so the lookup costs ~36ns where a bare get is ~20ns. Profiling the ray tracer (jolt-dad) showed keyword lookups are ~50% of a render and the guard is the only avoidable part, but dropping it needs to know statically that the subject is a plain struct. Type hints are exactly that information, and jolt already parses them and otherwise ignores them. This wires one through: a local hinted ^:struct asserts a plain struct/record map, so a (:k local) lookup on it skips the guard and emits a bare get. The hint rides on the binding symbol into the analyzer, which records it per-local and attaches it to :local IR nodes; the back end reads it on the lookup subject. It also propagates through inlining: when the inliner let-binds a non-trivial arg to a fresh local, it carries the called fn's param hint onto that local, so lookups inside the spliced body keep the bare path. This is a programmer assertion, like a Clojure type hint (an inaccurate hint just makes the raw get return the wrong value, the same contract as a wrong ^String), so it stays opt-in and off by default. On the ray tracer (with inlining on) this is 13.3s to 10.9s, 1.22x, taking it to 7.8x JVM from 9.4x after the inline pass. The unhinted path emits identical code (the fast arm is just factored out), so nothing changes without hints. Verified: a seeded full render produces an identical checksum hinted vs unhinted; conformance 335/335 in all three modes and the full jpm test pass; new test/integration/struct-hint-test.janet pins the guard removal, the inline propagation, and that an accurate hint is correctness-preserving.
This commit is contained in:
parent
e41832c05d
commit
c4be5d8a0e
4 changed files with 89 additions and 13 deletions
|
|
@ -178,7 +178,15 @@
|
|||
(let [op (get node :op)]
|
||||
(cond
|
||||
(= op :local) (let [r (get env (get node :name))]
|
||||
(if r r node))
|
||||
;; carry the param's ^:struct hint onto a let-bound fresh
|
||||
;; local, so lookups inside the inlined body keep the bare
|
||||
;; (no-guard) path (jolt-dad). The param hint asserts the
|
||||
;; arg is a struct; inlining doesn't change that contract.
|
||||
(if r
|
||||
(if (and (= :local (get r :op)) (get node :hint) (not (get r :hint)))
|
||||
(assoc r :hint (get node :hint))
|
||||
r)
|
||||
node))
|
||||
(= op :if) (assoc node
|
||||
:test (subst (get node :test) env)
|
||||
:then (subst (get node :then) env)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue