Build1 publisher2 min readPublished
gd-bench ships a runnable 40-line benchmark beside every flagged Godot array finding except packed arrays
Two pages of the Godot documentation disagree over whether packed arrays beat generic ones, so gd-bench scans .gd files and writes a measurement next to each finding. Its sample numbers are single-shot.
The Engineer · Build desk
What happened
- gd-bench installs from pip, scans every .gd file in a Godot project for array-type performance pitfalls, and with --gen-bench writes a runnable micro-benchmark per finding. It ships without dependencies and does not call an LLM.
- It reports four kinds of finding: untyped arrays that hold Variants, typed arrays that are still Variant-backed, packed arrays marked as already optimal, and dictionaries filled with sequential keys.
- The stated reason for measuring rather than advising is that the GDScript reference calls packed arrays slower than generic arrays while the class reference calls them faster, a contradiction tracked in godot-docs issue #10300.
- One generated script, run headless, printed 8231 usec for an untyped array against 5120 usec for a typed one, a ratio of 1.61x.
- Packed-array findings are deliberately excluded from benchmark generation, on the grounds that measuring the case that is already fast only adds noise.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- decision A team choosing between Array[int] and PackedInt32Array cannot cite either documentation page safely, so the cost of the decision becomes running a small script at each call site.
- capability The benchmark extends SceneTree and runs headless, so a build agent with no Godot editor installed can produce the number as part of CI.
- contradiction The tool's own dictionary cost claims rest on ratios the post asserts on its own authority, so a team adopting it inherits some of the unmeasured advice it was built to displace.
Each generated benchmark measures exactly two data structures doing exactly one operation, with no framework and no warm-up, and nothing that smooths the numbers statistically [12]. The sample run's gap between the two structures was 3111 usec [21]. That is one execution, and a second run could land somewhere else. The post itself notes that the same operation, append plus iterate, can flip the winner depending on element type and size [25]. For the sample figure to transfer to a stuttering enemy loop, that loop would have to append and iterate the same element type at a comparable count on the same Godot build.
The design rule is that any rewrite suggestion comes with the means to verify it [26], stated in the post as "always measure before rewriting" [24]. "Performance advice without a measurement is folklore," the author wrote on dev.to [13]. The script is 40 lines, short enough to read when the number looks wrong [12]. It extends SceneTree and runs headless, invoked as `godot --headless --script gd-bench-out/bench_gd_untyped_array_4.gd` [10].
The demo counts do not line up at first read. The summary for the seven-file project reports typed_array 5, dict_as_array 2, untyped_array 1 and packed_array 3 [14], which totals 11 [19]. The post elsewhere says the demo caught 8 findings across 5 files [15]. Drop the three packed-array entries, which by the tool's own rule get no script, and 11 becomes 8 [20]. The post leaves that reconciliation implicit.
The dict-as-array finding text asserts that a dictionary filled with sequential keys is about 2x slower and uses about half the memory again versus a plain Array [7]. The post does not show the run behind either figure. Both flagged call sites in the demo were doing 1000 sequential-key inserts per frame [15], and those are two of the findings that come with a generated script.
Detection is line-based. GDScript has no stable public AST, so the scan is regex pattern matching [17]. That is an honest limit. A matcher reading declaration lines sees `var inventory = []` and cannot see a container whose element type is settled by a function return further down the file. The test suite is 17 tests covering all four finding kinds, comment lines, non-sequential dictionary keys that should not be flagged, and end-to-end CLI runs [16]. The scanner also skipped the comment lines inside its own generated benchmarks and flagged only real declarations [18].
What to watch
- Whether godot-docs issue #10300 resolves which page is wrong about packed arrays versus generic arrays, which would remove the tool's founding premise.
- Whether the generated scripts gain repeat runs, since a single-shot 8231-versus-5120 timing gives no variance estimate.
- Whether detection moves off regex if GDScript ever exposes a stable public AST.