Repository navigation
Optimize HashGrid candidate traversal with matching cell groups - #282
Huangzizhou wants to merge 3 commits into
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #282 +/- ##
=======================================
Coverage 96.97% 96.98%
=======================================
Files 221 221
Lines 22426 22435 +9
Branches 1540 1538 -2
=======================================
+ Hits 21748 21758 +10
+ Misses 678 677 -1
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:
|
|
Hi @Huangzizhou, could you enable Allow edits by maintainers on this PR? I’d like to push a merge of main into this branch, along with a small update for the new trivially copyable candidates (#286). HashGrid now writes into To turn it on, open the PR page. In the right sidebar, tick the Allow edits by maintainers checkbox. The box is only visible to you as the PR author. |
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.