You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Document PyPy compatibility, JIT mechanism, and v1.4.8 comparison
Verified this session that Calysto Scheme's JIT needs no PyPy-specific
support: it compiles Scheme closures to Python source text and runs
them via compile()/exec(), which are Python-language builtins, not a
CPython API -- so the same code path produces CPython bytecode under
CPython and PyPy's own internal representation under PyPy, with no
branching on which runtime is hosting the process. Full 442-test suite
passes unmodified under PyPy 7.3.15 in an isolated venv, with metakernel/
yasi/jupyter_client/ipykernel/pyzmq all installing and importing cleanly
(numpy, needed only for notebook/%plot features, wasn't exercised).
Measured, steady-state, in-process: PyPy's own tracing JIT stacks with
this project's JIT rather than substituting for it -- ~2.1-2.4x on
JIT'd non-tail recursion (fib), ~60x on JIT'd tail loops (PyPy excels
specifically at the tight while-True loop shape Phase 4 already
flattens tail calls into). With (use-jit #f) forcing the plain
trampoline, PyPy alone is 9.5-13.6x faster than CPython at running it,
but still 57x-1000x+ slower than either runtime running the JIT'd
version -- confirming the two optimizations aren't substitutes.
Also measured v1.4.8 (the pre-JIT baseline; no perf-relevant changes
exist through v1.4.8) directly via a git worktree, rather than reusing
the older cross-version table's numbers from a different session/
methodology. Combined PyPy+JIT speedup over v1.4.8 ranges ~2,500x
(fib, non-tail recursion) to ~175,000x (large tail loops), with two of
the four sizes extrapolated from a measured per-call/per-iteration rate
since actually running v1.4.8 at those sizes would take 10-55 minutes
each.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
**Bottom line: roughly 2,500× to 175,000×, depending on the shape of code.**
1527
+
Non-tail recursion (`fib`) lands in the low-thousands×, since even PyPy's
1528
+
JIT can't eliminate the per-call closure/continuation cost this project's
1529
+
own JIT removes structurally. Tail loops land at tens-of-thousands to
1530
+
~175,000×, because that's exactly the shape both this project's tail-call
1531
+
flattening *and* PyPy's tracing JIT are individually strongest at, and they
1532
+
stack multiplicatively rather than substitute for one another.
1533
+
1534
+
Caveats:
1535
+
- The `fib(32)`/30M-loop `v1.4.8` numbers are calculated projections, not
1536
+
timed runs. The linearity assumption is solid for the tail loop (already
1537
+
validated at two points in the cross-version table above: 3,000 vs. 6,000
1538
+
iterations scaled linearly) and reasonable for `fib` (constant per-call
1539
+
dispatch cost regardless of which Fibonacci number is being computed), but
1540
+
they remain extrapolations.
1541
+
-`fib(20)`/`fib(26)`'s PyPy figures are single-shot (first call), not
1542
+
steady-state after a warmup call — small enough that PyPy's own JIT may
1543
+
not have fully engaged, which is why `fib(20)`'s PyPy number isn't
1544
+
reportable at all. The `fib(32)` row is the trustworthy one for
1545
+
PyPy-on-recursion, since it's steady-state after warmup.
1546
+
1547
+
---
1548
+
1413
1549
## Potential further improvements
1414
1550
1415
1551
Measured (not estimated) on this machine, see methodology after the table.
@@ -1425,7 +1561,7 @@ paths in `scheme.py` — not projected from the fib table.
1425
1561
|~~JIT: allow a parameter used in operator position~~|**Done — see Phase 6 above.**`(define (apply-twice f x) (f (f x)))`-shaped functions now JIT-compile; ~0.39s → ~0.19s on a 4,800-call benchmark (work time drops to immeasurably small, consistent with other JIT gains here). Surfaced and fixed a related missed-optimization bug in `_jit_call` itself |~~Low~~|
1426
1562
|~~JIT: drop the forward-ref-cell indirection for self-recursive (non-tail) calls~~|**Done — see Phase 7 above.**`fib`-shaped naive recursion **~2.3–2.8×** across three scales (`fib(20)`, `fib(30)`, `fib(37)`); tail/mutual-recursion/closure/HOF/`map`/`set!` benchmarks unaffected, as expected |~~Low~~|
1427
1563
|~~Interpreter: Phase 2's fallback re-executes a closure's entire body, including already-completed side effects, instead of resuming or staying on the trampoline from the point of failure~~|**Done — see Phase 8 above.**`_is_phase2_safe` gates `apply_proc`'s Phase-2 attempt on a static, transitive proof of safety instead of discovering failure mid-execution; the old silent retry is gone (a soundness gap in the checker would now crash loudly instead). Costs Phase 5/6's speedup for dynamic-dispatch shapes (~1.1–1.85× slower, measured); naive/tail/mutual recursion unaffected. Recovering that speed safely is tracked as a separate follow-up, not bundled with the correctness fix |~~Medium–High~~|
1428
-
| Run on PyPy |5–20× additional on top of existing gains (measured ~2.5×–10× on real workloads) — a user/deployment decision, not pursued further here| Zero code changes |
1564
+
|~~Run on PyPy~~|**Measured — see "Running under PyPy" above.** Full test suite (442 tests) passes unmodified under PyPy 7.3.15; JIT-on steady-state gains **~2.1–2.4×** (`fib`, non-tail recursion) to **~60×** (tail loops); a user/deployment decision, not a code change|~~Zero code changes~~|
1429
1565
1430
1566
**Bonus finding, not in the original list:**`(use-stack-trace #f)` — an
1431
1567
*already-shipped*, zero-code-change toggle — gives **~10–13%** wall-clock and
0 commit comments