Skip to content

perf: keep each surface's boundary curves for CoincidenceCheck (outputs bit-identical) - #1031

Merged
bherbruck merged 1 commit into
tangency-labs:mainfrom
meros:perf/memo-boundary-curves
Oct 26, 2026
Merged

bherbruck merged 1 commit into
tangency-labs:mainfrom
meros:perf/memo-boundary-curves

Conversation

@meros

@meros meros commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

This goes beyond upstream. The C++ makes the natural boundary curves of both surfaces again for every coincidence check, and a boolean runs that check for every face pair. This PR keeps those curves in each surface, so their curve caches are built once per surface. On the #948 loft at 40 sections the whole run now takes 94.0 G instructions, against 164.4 G on main and 127.8 G in the C++. No output changes, with the memo on or off.

Cause

SmSurface::CoincidenceCheck (SmSurface.cpp:7553-7580) calls CreateNaturalUVTrimCurves on both surfaces and deletes the curves at the end (SmObjsDelete). Their SmCacheMgr entries go with them. The port does the same, so with the curve cache memo (#992) the build count there equals the C++ count: 600 curve-cache builds at 20 sections (gdb on Tessellate). Each check rebuilds the same curves for the same surface. At 40 sections the check was 30 % of the C++ samples.

Change

  • Surface has a private field boundary_memo: BoundaryMemo, a OnceLock with one entry: the result of trim_curves (CreateNaturalUVTrimCurves) and its key.
  • Key. The domain's bits and the kernel inputs of the build: CurveMemoKey::of_kernel, the same tolerances and context scalars as the curve memo of perf: memoise curve caches in the curve value (outputs bit-identical) #992. A check with another key builds the curves and owns them, and it does not replace the entry.
  • Which surfaces. B-spline, plane, cone, sphere and torus surfaces are memoised: their CreateIsoParametricCurve overrides read nothing but the surface's own value. Offset, curve-bounded, step, volume, revolved and extruded surfaces always build.
  • Lending. base_coincidence_check borrows the curves (Cow::Borrowed). It only reads them, so nothing is copied. The curves carry their own curve cache memos (perf: memoise curve caches in the curve value (outputs bit-identical) #992), so each boundary curve's cache is built once per surface value.
  • Invalidation. A copy of a surface starts with an empty memo, as the copy constructor leaves m_pCacheObj null. kind_mut and the 6 editing methods that write self.kind replace the memo. Debug prints it as a constant.
  • Off switch. Kernel::memoize_curve_caches and the curve-memo-off feature (both from perf: memoise curve caches in the curve value (outputs bit-identical) #992) turn this memo off too.
  • Docs. The header of surface.surface_p8 describes the memo. The finding is in plan/findings/surface.surface_p8--coincidence-boundary-memo.md.

Why the memo cannot change a result

CreateNaturalUVTrimCurves takes &Kernel and the surface, and for the memoised kinds it reads only the surface's kind. Its result is a function of the surface value, the domain and the key, so a hit gives what a fresh build gives. A OnceLock fills once and is never changed afterwards, so the order in which checks run cannot matter.

  • Memory. There is one entry (4 curves and their caches) per live surface, freed with the surface.
  • Threads. The change uses no unsafe. Surface stays Send.

Test plan

Curve-cache builds per caller. gdb on Tessellate, 20 sections, the method of #992. Only CoincidenceCheck changes. The counts were taken on the same change before the rebase (on #992's 1a2f705dd); the rebase changed no code of this PR.

Phase Caller C++ main this PR
diff CoincidenceCheck 600 600 128
all 1393 1409 937

Instructions. The #948 skins, examples/toebox_loft.rs (local only). Release builds of main cf4dd475a (with #992) and of this branch, pinned to the 4 performance cores, 3 runs each, interleaved with the C++ (c9c5979). User-space instructions for the whole run (skins, outer - inner, ∩ box, volumes); they vary by less than 0.1 %.

main this PR C++
20 sections (median) 53.9 G 39.1 G (-28 %) 41.7 G
40 sections (median) 164.4 G 94.0 G (-43 %) 127.8 G

Bit identity.

  • New unit test boundary_memo_hit_is_the_fresh_build. Two overlapping B-spline patches give the same Coincidence with the memo off, on (first fill) and on again (hit). A copy starts empty, kind_mut empties the memo, and other tolerances miss and give the fresh result.
  • tests/curve_cache_memo.rs has two new cases with analytic and B-spline surfaces (skin - cylinder, skin ∩ sphere), so 8 booleans run with the memo off, on and on again. It compares every output value bit for bit, with the default features and with curve-memo-off.

Checks.

  • cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings, cargo clippy -p solidcore --all-targets --features curve-memo-off -- -D warnings and cargo check -p solidcore --target wasm32-unknown-unknown pass.
  • cargo test -p solidcore passes: 2,233 tests, 0 failed. tests/curve_cache_memo.rs with --features curve-memo-off passes.
  • difftest --unit on the same 31 units as perf: memoise curve caches in the curve value (outputs bit-identical) #992 (surface.surface_p8: 2108 cases), in both positions (--features cpp, and --features cpp,solidcore/curve-memo-off): 0 failed and max_rel_err=0e0 in each. The summary lines are the same in both positions.

Not covered

  • Revolved and extruded surfaces (they hold generator curves) and offset surfaces are not memoised.
  • The full --green difftest run (CI runs it).
  • Inputs other than these lofts and the test booleans.

Related

…ts bit-identical)

Not upstream: SmSurface::CoincidenceCheck makes both surfaces' natural
boundary curves again for every check, and a boolean runs it for every
face pair. Each Surface value now keeps them in a memo (BoundaryMemo, a
OnceLock), keyed by the domain's bits and the kernel inputs of the
build. The check borrows the curves, and the curves carry their curve
cache memos, so each boundary cache is built once per surface value.
Only B-spline, plane, cone, sphere and torus surfaces are memoised. A
copy starts with an empty memo; an edit through &mut Surface replaces
it. Kernel::memoize_curve_caches and the curve-memo-off feature turn it
off with the curve cache memo.

tangency-labs#948 loft, 20 sections: 128 curve-cache builds in CoincidenceCheck (C++
600), 937 in the whole run (C++ 1393). Whole-run instructions: 57.3 G
-> 41.9 G at 20 sections, 179.1 G -> 107.3 G at 40 (C++ 41.7 G,
127.8 G).

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

@bherbruck bherbruck left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved by the automated first-pass review: CI green, mergeable, no CI/build/dependency file changes, no red-flag patterns in added code (process/network/env/unsafe outside FFI/blobs/hidden unicode), 0 known-mismatch entries added. Logic is gated by CI and the difftest merge queue.

@bherbruck
bherbruck added this pull request to the merge queue Oct 26, 2026
Merged via the queue into tangency-labs:main with commit 01493bd Oct 26, 2026
13 checks passed
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.

2 participants