Repository navigation
Optimize HashGrid candidate traversal with matching cell groups - #289
Merged
Merged
Conversation
8 tasks done
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #289 +/- ##
==========================================
+ Coverage 96.77% 96.78% +0.01%
==========================================
Files 224 224
Lines 22772 22783 +11
Branches 1576 1575 -1
==========================================
+ Hits 22038 22051 +13
+ Misses 734 732 -2
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
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.
Description
Optimize HashGrid candidate traversal by joining already-sorted equal-key cell runs and enumerating only pairs inside matching cells. Same-type relations enumerate the upper triangle.
Cell descriptors are processed in batches of at most 65,536 cells. Same-type descriptors store one range instead of two; descriptor payload is bounded to 1 MiB for same-type queries and 2 MiB for cross-type queries on LP64, independent of occupied-cell count. Candidate buffers and deduplication storage still scale with generated candidates. Small cells share an outer parallel loop; dense cells retain the two-dimensional split with a grain size of 64 entries per axis.
Collision filters run before AABB overlap tests, preserving the legacy rejection order and avoiding the reject-heavy performance regression found during review. All new control statements have braces.
Applies to all six candidate relations, including static and swept bounds. Public API/ABI, C++17 requirements, broad-phase selection, and candidate sorting/deduplication remain unchanged. No ESP evaluator or PolyFEM solver changes are included.
Base:
b9d03258956c94f7bc8d861929430dc7a9473458. Reviewed version:b2b9769. Updated version:cc352d1.Type of change
Validation
ipc_toolkit_tests '[hash_grid][regression]' --rng-seed 282: 4 cases / 2,222 assertions passed. Includes exact ordered IDs, all six relations against brute force, 2D/3D, static/swept bounds, filters, inflation, empty relations, clear/reuse, and full/partial cell batches at 65,535 / 65,536 / 65,537 cells.ipc_toolkit_tests '[broad_phase]' --skip-benchmarks --rng-seed 282: 23 cases passed / 1 skipped / 2,930,505 assertions passed. The skip needs private benchmark meshes.git diff --check.Performance on identical inputs
The following results measure traversal separately from grid construction and nonlinear-solver trajectories. A/B/C denote base / reviewed / updated versions. All use the same cached Release dependencies, with the side-by-side source variants compiled using C++17 and
-O3(without-march=native). Numbers are medians of three process-level median timings, alternating process execution order, on macOS arm64.Sparse and reject-heavy regression workloads
Prebuilt sorted cell runs isolate candidate traversal. Each process performs one warmup query and then 7 measured sparse queries or 9 measured reject-all queries. Both workloads return zero candidates:
Peak memory: whole-process peak RSS for the sparse workload at the 16-thread limit is 82.98 / 131.16 / 84.08 MiB for A/B/C. B's growing cell array caused a large transient allocation; C is within approximately 1.09 MiB of the base in this case. This is a workload-specific RSS measurement, not a general memory-reduction claim.
The repository contains the same input patterns in
tests/src/tests/broad_phase/benchmark_broad_phase.cpp. To run its Catch2 benchmarks:ipc_toolkit_tests '[hash_grid][traversal]' --benchmark-samples 9 --benchmark-resamples 100 --benchmark-warmup-time 100Catch2 reports its own statistical estimates; the table above uses the separate fixed-query process-median protocol described above.
Fixed-state mat-twist replay
Replay saved surface geometry from the baseline trajectory of the pinned PolyFEM test-data scene
contact/examples/3D/stress-tests/mat-twist.json(c103d197b7f4d5474208c9b0a470c81349e75dd7). Each input has 3,200 vertices / 9,594 edges / 6,396 boundary triangles. Static queries use the saved state at the indicated step; swept queries use the previous and indicated saved states. All bounds use inflation radius 0.0005. Each process builds the same input once, warms up once, and measures 9 repetitions of all six candidate relations. The reported time is for the combined six queries, excluding construction, input loading, and the solver.Every returned candidate pair, its orientation, and its sequence position match exactly across all three implementations for these six replay cases. Restoring filter-first order trades a few percent relative to B on some replay inputs for the reject-heavy improvement and legacy filter behavior; C remains faster than A in all measured cases. These are traversal measurements on fixed inputs, not new end-to-end simulation timings.
Earlier end-to-end run
The full 300-step simulation results below belong to the previous implementation at
b2b9769. They are retained as historical measurements; the updatedcc352d1revision is measured above using identical-input replay. The historical simulations take different trajectories, so their total times and RSS do not isolate broad-phase cost.Historical 300-step benchmark at b2b9769
Plain IPC performance: PolyFEM mat-twist
Both variants completed 300/300 timesteps from rest, t=0–12 s at dt=0.04 s, using 16 threads. Only PolyFEM test-data
contact/examples/3D/stress-tests/mat-twist.jsonwas run, pinned toc103d197b7f4d5474208c9b0a470c81349e75dd7: 3,200 vertices / 9,126 tetrahedra, plain IPC, ESP/GCP disabled, HashGrid.Positive percentages mean reductions in B; negative values mean increases. Forward time is summed from CSV statistics; component timers use rounded trace values and overlap. Process timings include startup, output, and shutdown.
Solver configuration and validation
Both use the same reduced Newton settings:
{ "solver": { "nonlinear": { "Newton": { "use_psd_projection": false, "use_psd_projection_in_regularized": true } } } }Ordinary projected fallback is disabled; regularized Newton uses PSD projection. Materials, boundary conditions, mass lumping, stopping tolerances, the 500-iteration nonlinear limit, and augmented-Lagrangian settings are unchanged. Linear solver:
Eigen::AccelerateLDLT. These settings apply only to the benchmark; no PolyFEM solver changes are included in this PR.Each run logged 300 successful nonlinear solves, all stopping on gradient norm, with no solver errors. All 301 saved states per run had finite fields, positive tetrahedron volumes, prescribed boundary motion, and no surface intersections detected with independent SweepAndPrune. Minimum volume ratios: A 0.383577894, B 0.382036348; maximum boundary error 9.71e-17. All 300 native checkpoints per run passed finite-state and timestep-metadata validation.
Reference meshes and topology match, but trajectories differ: nonlinear iteration counts differ at 258/300 steps, and maximum vertex-displacement difference is 0.164897149 scene units (step 300: 0.0755031643). These are not numerically identical trajectories.