Repository navigation
perf(examples): cut Bunnymark's update loop by a quarter - #15
Conversation
At 10,000 sprites the instanced frame is bound by updateBunnies. The loop read three module-level constants per sprite, each an OP_GET_GLOBAL on GocciaScript 0.14.0, and added half the texture size in every edge test. The constants are now locals and the edge tests compare against bounds computed once per call: 88.76 opcodes per sprite become 78.76, and the loop falls from 25.6 to 19.1 ms per frame on a Ryzen 9 6900HX. The rendered frame is unchanged. The bound form differs from the summed form only for the one double just below the top bound, where the sum rounded up; benchmarks/import-binding-aliases.md records that and the measurements. A new unit test pins all four edges.
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Central YAML (base), Organization UI (inherited) Review profile: CHILL Plan: Team Run ID: 📒 Files selected for processing (3)
Limit details: You’ve used the included review currently available. Your 98 included PR review attempts over the past 7 days set your current allowance at 1 review per hour. 📝 WalkthroughWalkthroughThe bunny update loop now uses strict centre-coordinate bounds for bounce checks and caches loop values locally. A unit test checks edge behavior across two updates. Benchmark notes report opcode counts, timings, motion equivalence, and a floating-point edge case. ChangesBunny update loop
Priority: ⬇️ Low Estimated code review effort: 2 (Simple) | ~10 minutes Change: Refactor Merge Risk: ⚪ Minimal · up to The optimization preserves the intended bounce and transform behavior, with a documented floating-point boundary tradeoff. Ready to merge after normal test checks. 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Comment |
At 10,000 sprites the instanced Bunnymark frame is bound by
updateBunnies, not by rendering: about 27.6 ms of a 28.2 ms frame on the Radeon RX 6600M. This takes about a quarter off that loop with the same rendered frame and default syntax.bunnyInstanceStride,bunnyXOffsetandbunnyYOffsetwere read per sprite, and GocciaScript 0.14.0 compiles a module-levelconstread inside a function toOP_GET_GLOBAL.Together that is 88.76 opcodes per sprite down to 78.76.
addBunniesis unchanged; it only runs when sprites are added.Measurements
Ryzen 9 6900HX, Linux Mint 22.3, GocciaScript 0.14.0, 10,000 fixed-seed sprites, 100 frames per run, three interleaved runs. "Before" is tag
0.1.0, whose examples are identical tomain.DrawMeshInstanced, llvmpipe under XvfbDrawTextureV, llvmpipe under XvfbBoth draw paths lose about 6 ms per frame, which matches the saving in the loop alone. The per-run times and the opcode profile are in
benchmarks/import-binding-aliases.md.Not measured: the two Radeon renderers and the two Macs. On hardware the instanced frame is almost entirely this loop, so the gain there should be larger than under llvmpipe, but that is an expectation, not a result.
Equivalence
The graphical integration test still finds the direct and instanced frames byte-identical, with the same hash as before (
e22537b0…). Running the old and new loop side by side over seven configurations of seed, frame time, texture size and screen size left every position, velocity and instance transform identical across 297,401 edge reversals.The two forms of an edge test are not equal for every double, though.
y + 16 - 40 < 0andy < 24disagree for the one double just below 24: the sum rounds up to 40, so the old loop did not reverse a sprite at exactly that position and the new one does. With the example's 1280x720 window and 32-pixel texture that is the only position where the loops differ. A variant that keeps the sums, and is therefore exact for every double, measured about 21.6 ms per frame; I chose the bounds for the extra 2 ms.A new unit test pins all four edges: a sprite reverses past an edge but not on it. It passes against the previous loop as well.