Scope whole-program optimization to app namespaces (jolt-87e) (#126)
Under JOLT_OPTIMIZE a -m program run inferred + specialized EVERY loaded namespace, including every transitive dependency. On a dep-heavy app that's prohibitive: malli-app cold-started in ~2m10s (hundreds of dep namespaces, each run through the per-form inline + inference passes). The closed world a whole-program pass reasons over is the APP, not its libraries. jolt-deps now passes the project's own source roots (its deps.edn :paths) to the runtime as JOLT_APP_PATHS. A namespace loaded from an app root gets full optimization (and joins the one whole-program fixpoint); a dependency namespace compiles at default cost — :inline? off for its load, so the per-form optimize passes don't run over library code — staying direct-linked but generically typed (the open-world default). With no app roots declared (a bare program run, or jolt without jolt-deps) everything counts as app, so behavior is unchanged. malli-app JOLT_OPTIMIZE cold start: 2m10s -> 4.5s. Compute-heavy programs whose hot code is their own namespaces (the typed ray tracer) are unaffected — their code is app code and still fully optimized (9s/frame render). Applied at runtime in main for the same baked-at-build-time reason as JOLT_PATH; added to the ctx-image cache key. Help text corrected: optimization is opt-in, not default. Co-authored-by: Yogthos <yogthos@gmail.com>
This commit is contained in:
parent
d200725811
commit
d223f40c91
7 changed files with 159 additions and 12 deletions
67
test/integration/app-scope-wholeprogram-test.janet
Normal file
67
test/integration/app-scope-wholeprogram-test.janet
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
# Whole-program inference scoped to app namespaces (jolt-87e).
|
||||
#
|
||||
# Auto-whole-program (a -m program run under direct-link) used to defer EVERY
|
||||
# loaded namespace — including every transitive dependency — into one closed-
|
||||
# world fixpoint, which is prohibitive on dep-heavy apps (hundreds of dep nses;
|
||||
# a ~2-minute cold start on malli). With app source roots declared (JOLT_APP_PATHS
|
||||
# / jolt-deps, here :app-paths), only the app's OWN namespaces join the whole-
|
||||
# program batch; dependency namespaces skip inference (they stay direct-linked
|
||||
# but generically typed — the open-world default). With NO app roots declared,
|
||||
# every namespace is treated as app (whole-program over everything, pre-87e).
|
||||
|
||||
(use ../../src/jolt/api)
|
||||
|
||||
(var failures 0)
|
||||
(defn- check [label got want]
|
||||
(unless (= got want)
|
||||
(++ failures)
|
||||
(printf "FAIL [%s] got %q want %q" label got want)))
|
||||
|
||||
# Lay down an app root and a dep root under a tmp dir.
|
||||
(def tmp (string (os/getenv "TMPDIR") "jolt-87e-" (os/time)))
|
||||
(def app-root (string tmp "/app"))
|
||||
(def dep-root (string tmp "/dep"))
|
||||
(os/mkdir tmp) (os/mkdir app-root) (os/mkdir dep-root)
|
||||
(spit (string dep-root "/mydep.clj")
|
||||
"(ns mydep)\n(defn helper [x] (+ x 1))\n")
|
||||
(spit (string app-root "/myapp.clj")
|
||||
"(ns myapp (:require [mydep]))\n(defn run [x] (mydep/helper x))\n")
|
||||
|
||||
(defn- with-wp [f]
|
||||
(def saved (os/getenv "JOLT_WHOLE_PROGRAM"))
|
||||
(os/setenv "JOLT_WHOLE_PROGRAM" "1")
|
||||
(defer (os/setenv "JOLT_WHOLE_PROGRAM" saved) (f)))
|
||||
|
||||
# --- app roots declared: only the app ns defers into the batch ---------------
|
||||
(with-wp
|
||||
(fn []
|
||||
(let [ctx (init {:compile? true :direct-linking? true
|
||||
:paths [app-root dep-root] :app-paths [app-root]})]
|
||||
(check "whole-program is on" (truthy? (get (ctx :env) :whole-program?)) true)
|
||||
(eval-string ctx "(require '[myapp])")
|
||||
(let [deferred (or (get (ctx :env) :inferred-nses) @[])]
|
||||
(check "app ns deferred to batch" (truthy? (index-of "myapp" deferred)) true)
|
||||
(check "dep ns NOT in batch (per-ns inferred)"
|
||||
(truthy? (index-of "mydep" deferred)) false))
|
||||
# still runs correctly after the (scoped) whole-program pass
|
||||
(when-let [ip (get (ctx :env) :infer-program!)] (protect (ip ctx)))
|
||||
(check "scoped whole-program program still correct"
|
||||
(eval-string ctx "(myapp/run 41)") 42))))
|
||||
|
||||
# --- no app roots declared: every ns defers (pre-87e whole-program) ----------
|
||||
(with-wp
|
||||
(fn []
|
||||
(let [ctx (init {:compile? true :direct-linking? true
|
||||
:paths [app-root dep-root]})]
|
||||
(eval-string ctx "(require '[myapp])")
|
||||
(let [deferred (or (get (ctx :env) :inferred-nses) @[])]
|
||||
(check "no app roots: app ns deferred" (truthy? (index-of "myapp" deferred)) true)
|
||||
(check "no app roots: dep ns ALSO deferred"
|
||||
(truthy? (index-of "mydep" deferred)) true))
|
||||
(when-let [ip (get (ctx :env) :infer-program!)] (protect (ip ctx)))
|
||||
(check "unscoped whole-program still correct"
|
||||
(eval-string ctx "(myapp/run 9)") 10))))
|
||||
|
||||
(if (pos? failures)
|
||||
(do (printf "app-scope-wholeprogram: %d failure(s)" failures) (os/exit 1))
|
||||
(print "app-scope-wholeprogram: all cases passed"))
|
||||
Loading…
Add table
Add a link
Reference in a new issue