BlueQuartzSoftware / BlueQuartzSoftware/simplnx
SurfaceNets: MMCellMap::nodeType re-derives per-vertex data that init() already computed
- Dominant language
- C++
- Stars
- 17
- Forks
- 13
- Avg merge
- 3d 4h
- Merged PRs (30d)
- 10
Description
## Summary
`MMCellMap::nodeType()` re-derives data that `MMCellMap`'s own initialisation already computed and threw away. It costs 8 scattered, bounds-checked `label()` reads plus a small distinct-count loop **per output vertex**, replacing what used to be a single cached bit-field read.
This is not a correctness problem and may well be lost in the noise — filing it with the measurement so someone can decide with a profile rather than a guess.
## Background
SurfaceNets used to write `MMCellFlag::numJunctions()` directly as its Node Types — a count of junction face-crossings in the range 0-6. That collided with `NodeType::Unused == 0` and did not match the convention QuickSurfaceMesh and M3CSurfaceMeshing use, so it was changed to compute the shared convention (`min(distinct corner labels, 4)`, `+10` when any corner is exterior padding) from the cell's 8 corner labels.
`numJunctions()` was a bit-field read off `MMCellFlag::m_BitFlag`. The replacement gathers the 8 corner labels afresh.
## The cost
Per **output vertex** (call it `V`, the surface-vertex count — not the voxel count `N`):
- 8 calls to `MMCellMap::label()`, each doing an index computation, a range check, and an array read;
- an O(64) worst-case distinct-count loop over those 8 values.
The access pattern is unfriendly: the 4 "near" corners are stride-1 and stride-`dimX` apart, but the 4 "far" corners sit a full XY-plane away — roughly 1 MB at 512³ — so most of the 8 reads are likely L2 misses.
**The same 8-corner gather already happens once per cell** during initialisation, to build `pCell->flag`, and is then discarded. So this is a genuine re-derivation of data the code already had in hand.
Mitigating context, for honesty: `V` is much smaller than `N`, and this is the same order of work as the `getEdgeQuad()` calls already performed for that vertex. It may not be measurable.
## Suggested fix
Compute the node type during initialisation, where the 8 labels are already loaded, and store it.
`MMCellFlag::m_BitFlag` is a `uint32` with `numJunctions` occupying bits 29-31, so there may be spare bits to hold the node type (it needs 4 bits to represent 2/3/4/12/13/14) at **zero** memory cost. That would be the ideal shape.
**Audit the spare bits first.** If they are not available, the fallback — a byte on each `Cell` — costs one byte per voxel, which is ~134 MB at 512³ and clearly not worth it for this.
Note `MMCellMap.*` / `MMCellFlag.*` are third-party-derived (Sarah Frisken, Brigham and Women's Hospital), so a bit-layout change there deserves care.
## How to decide
Profile `SurfaceNetsFilter` on a large volume (512³ or bigger) and look at the share of time in `MMCellMap::nodeType` / `getCornerLabels` / `label`. If it is not visible, close this as not worth the risk.
Contributor guide
Assessment
This issue has not been assessed yet.