Skip to content

Avoid boxed captured variables in forcexyz, clipping and reconstruct - #530

Open
evetion wants to merge 6 commits into
mainfrom
perf/boxing
Open

evetion wants to merge 6 commits into
mainfrom
perf/boxing

Conversation

@evetion

@evetion evetion commented Oct 23, 2026

Copy link
Copy Markdown
Member

Towards #371. Scanning the lowered code of all GeometryOps and GeometryOpsCore methods for Core.Box found 9 methods with boxed captured variables (same on Julia 1.10 and 1.13). This fixes 8 of them, in three commits:

  • forcexyz reassigned the captured z inside its apply closure, and called GI.is3d on the whole input, which throws a MethodError for a vector of geometries even though the docstring says vectors work. It now checks each point, which is also what the docstring describes. A new test covers the vector case.
  • Polygon clipping: the counters in _build_a_list and last_a_idx in the DoubleNaturalTree traversal are now Refs, and the conditionally reassigned multipoly_a in the multipolygon difference/union methods is let-bound, so those now infer a concrete return type.
  • reconstruct (GeometryOpsCore): the iteration state is threaded through a Ref. This needs a GeometryOpsCore version bump, since GeometryOps pins =0.1.12.

The 9th, in ThinnedDoubleNaturalTree, is left alone: it's a typed box with no measurable cost, and that method gives wrong results anyway (#528).

Julia 1.13, median, same results (hashes of the output coordinates) before and after:

before after
forcexyz, 10k point polygon 941 µs, 30,034 allocs 3.5 µs, 11 allocs
reconstruct, 25 polygons x 201 points 1.18 ms, 50,310 allocs 3.2 µs, 179 allocs
intersection with DoubleNaturalTree, 4000 x 4000 vertices 1.02 ms, 53,537 allocs 0.34 ms, 68 allocs
intersection, default (NestedLoop), 400 x 400 2.20 ms 2.00 ms
difference of two 3 x 200 multipolygons 5.03 ms 4.58 ms
union of two 3 x 200 multipolygons 1.91 ms 1.74 ms

The full test suite gives the same results as main, plus the two new forcexyz tests. CI will hit the stale overlay_ng.jl:303 test that already fails on main (fixed in #520).

🤖 Generated with Claude Code

evetion and others added 3 commits October 23, 2026 09:33
`forcexyz` reassigned the captured `z` inside its `apply` closure, so Julia
boxed it, and it called `GI.is3d` on the whole input, which fails for
vectors of geometries. Checking each point is what the docstring describes.
This is ~270x faster on a 10k point polygon.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Counters reassigned inside closures in `_build_a_list` and the
`DoubleNaturalTree` edge traversal, and the conditionally reassigned
`multipoly_a` in the multipolygon `difference`/`union` methods, were boxed.
Use `Ref`s and a `let` block instead. `DoubleNaturalTree` intersection of
two 4000-vertex polygons goes from 1.0 ms to 0.34 ms, the default clipping
path is ~9% faster, and `difference`/`union` of multipolygons now infer a
concrete return type.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The state was reassigned inside `map` closures, so it was boxed and every
component cost a dynamic dispatch. Reconstructing 25 polygons of 201 points
goes from 1.2 ms to 3.2 μs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@asinghvi17

Copy link
Copy Markdown
Member

Thanks, looks awesome! And with the julia 1.12 improvements to allocation elision it should allocate even less than before.

Looks like something small broke in the polygon clipping code, I'm setting a bot on it.

asinghvi17-bot and others added 3 commits October 24, 2026 08:16
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants