Skip to content

SurfaceNets: MMCellMap::nodeType re-derives per-vertex data that init() already computed #1707

Description

@imikejackson

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions