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.
Summary
MMCellMap::nodeType()re-derives data thatMMCellMap's own initialisation already computed and threw away. It costs 8 scattered, bounds-checkedlabel()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 withNodeType::Unused == 0and did not match the convention QuickSurfaceMesh and M3CSurfaceMeshing use, so it was changed to compute the shared convention (min(distinct corner labels, 4),+10when any corner is exterior padding) from the cell's 8 corner labels.numJunctions()was a bit-field read offMMCellFlag::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 countN):MMCellMap::label(), each doing an index computation, a range check, and an array read;The access pattern is unfriendly: the 4 "near" corners are stride-1 and stride-
dimXapart, 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:
Vis much smaller thanN, and this is the same order of work as thegetEdgeQuad()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_BitFlagis auint32withnumJunctionsoccupying 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
SurfaceNetsFilteron a large volume (512³ or bigger) and look at the share of time inMMCellMap::nodeType/getCornerLabels/label. If it is not visible, close this as not worth the risk.