Repository navigation
perf: copy-on-write NURB storage, so evaluation copies share the control net (outputs bit-identical) - #1088
Merged
Conversation
…rol net (outputs bit-identical) Two copies on the tangency-labs#948 loft copy whole NURB nets that neither side writes: the IsoCurve that SurfPtSolveStepper::find_boundary_solution builds over an evaluation copy of its surface (61 of 7952 profile samples), and the private B-rep snapshot of curve_on_classify (Brep::clone_sharing_surface_caches, 98 samples: surface and curve copies). Neither can borrow: Curve owns its geometry ('static enum), and Arc<geometry::Surface> would make Curve !Send (Surface holds a RefCell). The NLib storage itself is plain data, so srf_basic::Surface (net, u, v) and crv_basic::Curve (pw, u) now hold it in Arc and write through Arc::make_mut. A clone shares the storage until one side writes; compress keeps compact storage shared (same values), and so does compressed_copy (the one-pass copy of tangency-labs#1006); the copy constructor's knot fix-up copies only the knot vector it writes. The fields were already private to their modules, so every write is in these two files. Loft driver, 3 interleaved runs, P-cores, medians: user cycles -5.5 % (20 sections) and -9.4 % (40); all non-timing output identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bherbruck
approved these changes
Oct 26, 2026
bherbruck
left a comment
Collaborator
There was a problem hiding this comment.
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
In short: the kernel makes many short-lived copies of spline curves and surfaces, and each copy duplicated the full control-point grid although neither copy changes it. The grid is now shared between copies and duplicated only when one side writes to it. Results do not change. On the loft boolean of #948, user CPU cycles fall by 2.4 % (20 sections) and 3.1 % (40 sections). This changes how every spline curve and surface stores its data.
What is wrong
Two copy sites on the #948 loft (20 sections, 7952 profile samples on main
d4940b63e) copy whole NURB nets that neither side writes:SurfPtSolveStepper::find_boundary_solutionIsoCurve::from_surface(surface.clone_for_evaluation()?): the iso curve owns its surfaceCurveis a'staticenum that owns its geometry;Arc<surface::Surface>would makeCurve!Send(the geometrySurfaceholds aRefCellthroughCrvOnSurf)curve_on_classify->Brep::clone_sharing_surface_cachesIn both, what is copied is mostly NLib storage (
srf_basic::Surfacenet and knots,crv_basic::Curvecontrol points and knots), and nobody writes it.Arc+Arc::make_mutcompressof storage that is already compactChange
srf_basic::Surfaceholdsnet,u,vasArc<Vec<..>>;crv_basic::Curveholdspw,uthe same way. Every write goes throughArc::make_mut. The fields were already private to their modules, so all writes are in these two files.compressreturns early when the storage is already exactly the indices in use (then it would build the same values).compressed_copy(the one-pass copy from perf: stop copying NURB surfaces for out-of-bounds solves and tolerance tests (outputs bit-identical) #1006) returnsself.clone()when the storage is already compact, so the copy constructor shares the net too.Clone,PartialEqandDebugbehave as before on the values.Arcof plain data keeps both typesSend + Sync.Test plan
toebox_loft <sections> 1 full(the #948 driver, a local example, not in this PR), release build, mainb206d3f5aagainst this branch, 3 interleaved runs each,taskset -c 0-3,perf stat -e instructions:u,cycles:u,task-clock, medians (load 1 to 3):Per-site samples (frame-pointer builds,
perf record -F 1000, 20 sections, 2 runs each):find_boundary_solutionclone_sharing_surface_cachessnapshotThe rest of the snapshot is the copy constructor's knot fix-up (
fixup_knot_vector, upstream work) and the topology arrays. All non-timing output (counts, volumes and bounding boxes to 17 digits) is identical across all runs of main and the branch, for both inputs.Checks
cargo fmt --all --check: passcargo clippy --workspace --all-targets -- -D warnings: passcargo test -p solidcore: 2276 passed, 0 failed. Two new tests: a clone shares the storage,compressandcompressed_copyof compact storage keep it shared, and a write unshares only what it writes, leaving the original unchanged (surface and curve).cargo check -p solidcore --target wasm32-unknown-unknown: pass--unit, allfailed=0 deferred=0 max_rel_err=0e0:Not run
difftest --green(the CI gate, every green and partial unit) did not run locally; only the 9 units above ran. CI runs the full set.What follows
b206d3f5a(after perf: stop copying NURB surfaces for out-of-bounds solves and tolerance tests (outputs bit-identical) #1006, perf: memoise curve caches in the curve value (outputs bit-identical) #992 and perf: build a missing polar box without copying the surface cache in AdvSurfaceIntersector (outputs bit-identical) #1033);git merge-treeis clean against perf: keep each surface's boundary curves for CoincidenceCheck (outputs bit-identical) #1031 (dd47b13b9) and the current main4fa6661e2.Refs #948.