Skip to content

ENH: Out-of-core architecture rewrite and filter optimizations - #1568

Open
joeykleingers wants to merge 7 commits into
BlueQuartzSoftware:developfrom
joeykleingers:worktree-ooc-architecture-rewrite
Open

ENH: Out-of-core architecture rewrite and filter optimizations#1568
joeykleingers wants to merge 7 commits into
BlueQuartzSoftware:developfrom
joeykleingers:worktree-ooc-architecture-rewrite

Conversation

@joeykleingers

@joeykleingers joeykleingers commented Mar 24, 2026

Copy link
Copy Markdown
Contributor

Summary

This branch does two things at once:

  1. Reworks the core out-of-core (OOC) data-storage architecture in simplnx so that disk-backed storage is supplied by a runtime-registered IO manager through clean interfaces, with the core library holding no reference to any concrete OOC format or symbol. Core gains a bulk-I/O data-store API (copyIntoBuffer / copyFromBuffer), an injectable store-format resolver, IO-manager lifecycle hooks, a tri-state storage-mode preference, a memory-budget manager, and memory-safety guards. The OOC implementation itself (chunked HDF5 stores, the chunk cache, deflate decompression) lives in a separate private SimplnxOoc source set and is not part of this diff — it plugs into core at runtime through these interfaces.

  2. Adds chunk-aware (OOC-optimized) algorithm variants for ~50 filters across the SimplnxCore and OrientationAnalysis plugins, plus shared core utilities that benefit many more. Random-access algorithms that thrash the chunk cache on disk-backed arrays now either stream data with bulk I/O or dispatch to a sequential "scanline / CCL" variant, eliminating per-element virtual dispatch and HDF5 chunk churn. In-core behavior and performance are preserved via a runtime storage-type check, so optimized filters keep two code paths: one for in-memory data, one for disk-backed data.

Scope of this diff

develop…HEAD: 378 files, +42,853 / −13,112.

Area Files + / −
src/simplnx (core library) 74 +6,874 / −1,252
Plugins/SimplnxCore 179 +24,817 / −8,795
Plugins/OrientationAnalysis 103 +8,379 / −2,853
Root / build / top-level tests 22 +2,783 / −212

How to review

The diff is large but highly patterned, and the two layers are independent:

  • Core architecture lives in src/simplnx/DataStructure/IO/Generic/*, Core/Preferences.*, Utilities/{MemoryBudgetManager,AlgorithmDispatch,SegmentFeatures,UnionFind,SliceBufferedTransfer}.*, and Filter/IFilter.cpp (Part 1).
  • Filter work is summarized in the inventory tables in Part 2. Every optimization is one of four patterns — reviewing one example of each pattern transfers to the rest. Each optimized algorithm's original file was renamed to the in-core variant and the OOC variant added beside it, so the diffs read as edits against the original code rather than wholesale new files.

Part 1 — Core out-of-core architecture (src/simplnx)

1.1 Bulk-I/O data-store API

  • copyIntoBuffer / copyFromBuffer on AbstractDataStore<T> (a flat range form and a tuple-addressed form) are the single mechanism for bulk reads/writes, implemented by DataStore<T> and EmptyDataStore<T>; the OOC implementation supplies its own override.
  • The storage-kind signal is IDataStore::StoreType { InMemory, OutOfCore, Empty }, queried via getStoreType(). The chunk-traversal API (loadChunk, getNumberOfChunks, getChunkLowerBounds, getChunkUpperBounds, getChunkShape) and the chunk-shape-based OOC detection are removed — bulk I/O plus getStoreType() replace them entirely.
  • Empty/placeholder string storage: EmptyStringStore, AbstractStringStore/StringStore placeholder support, and StringArray::isPlaceholder().

1.2 Runtime store-format resolution and IO-manager hooks

OOC capability is supplied entirely at runtime; core never names the concrete format.

  • IDataStoreFormatResolver: a const, thread-safe policy interface deciding which registered format a soon-to-be-created array uses ("" = in-memory). InMemoryFormatResolver is the default policy.
  • ArrayCreationUtilities::ResolveStorageFormat is the single decision point for every array-creation call site, with a fixed precedence: the unstructured/poly-geometry gate (ParentGeometrySupportsOoc) forces in-core, then an explicit per-filter format override, then the DataStructure's resolver. DataStructure carries a per-instance resolver plus a lazily-seeded process-wide default (neither serialized).
  • IDataIOManager lifecycle hooks (default no-ops): finalizesImport, onImportFinalize, onRecoveryWrite, onFinalizeStores, setBaseDirectory, shutdownManager. DataIOCollection fans these out to every registered manager, so an OOC manager participates in import finalization, recovery writes, store read-only transition, and shutdown without core knowing the specifics.
  • CoreDataIOManager: the always-present default manager that registers the in-memory data-store/list-store factories; an OOC manager registers on top at runtime.
  • Store creation goes through resolver-aware CreateDataStore / CreateListStore single entry points; CreateNeighborListAction / CreateNeighbors thread an explicit dataFormat override through to the store.

1.3 Storage-mode preference (+ legacy migration)

  • DataStorageMode { Adaptive, ForceInCore, ForceOutOfCore } (persisted as data_storage_mode) is the single source of truth for storage placement, replacing the large_data_format / force_ooc_data preferences. The enum is deliberately OOC-vocabulary-free — core states user intent; the registered manager maps it to a concrete format. dataStorageMode() migrates older preference files from the retained legacy keys; useOocData() is a convenience view (true unless ForceInCore).
  • Preferences seeds the OOC base directory and default format on startup via the IO-collection hooks, and gains a removeValue helper.

1.4 Memory budget + memory safety

  • MemoryBudgetManager: tracks the budget governing in-core vs OOC placement. defaultBudgetBytes() (50% of RAM, ≥1 GiB, clamped to the cap), maxBudgetBytes() (max(min(total−6 GiB, 0.95·total), 1 GiB)), and setBudgetBytes() which clamps to the cap and reports whether it clamped. Total-RAM detection is centralized in Memory::GetTotalMemory(). The default is clamped to the cap so it never exceeds it on <12 GiB machines (e.g. CI runners).
  • nxrunner --memory-budget <GB>: CLI flag wired through to the budget manager so the override takes effect in headless runs, with parsing hardened against NaN/inf/trailing garbage.
  • Memory-safety guards (additive to the existing -264 total-RAM hard block):
    • -271 — non-blocking preflight warning when an in-core array would exceed currently-available RAM. OOC arrays are excluded (EmptyDataStore::memoryUsage() reports 0 for OOC placeholders; the format is resolved in preflight via the shared ResolveStorageFormat helper).
    • -272 — a std::bad_alloc safety net at the single IFilter::execute → executeImpl boundary, turning an out-of-memory condition into a clean pipeline error instead of a crash.

1.5 HDF5 streaming write + compression

  • DatasetIO provides the streaming OOC write path — createEmptyDataset + writeSpanHyperslab for arrays too large to be resident — alongside the single-shot writeSpan; reads use readChunk / readChunkIntoSpan.
  • The streaming path honors requested compression: createEmptyDataset builds the chunked-deflate creation property list (BuildChunkedDeflateDcpl), so a large OOC array taking the two-step streaming write is compressed identically to the single-shot path. Contiguous storage is still used for compression level 0 and for arrays below the small-array threshold.

1.6 .dream3d loader API

  • Dream3dIO exposes a LoadDataStructure family: LoadDataStructure, LoadDataStructureMetadata (metadata-only for preflight), LoadDataStructureArrays (array subset), plus resolver-aware overloads that stamp a per-DataStructure resolver before import finalization (so a read-only visualization load can direct arrays to disk for fast first-show). Import is eager or deferred based on anyManagerFinalizesImport(); writes run under a recovery-write guard supplied by the registered manager.
  • ImportH5ObjectPathsAction uses this API: metadata-only on preflight, full load on execute, with a selective shortest-path-first merge of only the requested paths.

1.7 Core algorithm infrastructure

  • AlgorithmDispatch.hppDispatchAlgorithm<InCoreAlgo, OocAlgo>(arrays, args…), a free function template selecting between an in-core and an OOC algorithm class at runtime. Priority: ForceInCoreAlgorithm() > any array OOC > ForceOocAlgorithm() > in-core default. RAII test guards ForceOocAlgorithmGuard(bool) and ForceInCoreAlgorithmGuard let a single build exercise both paths.
  • SegmentFeatures OOC pathexecuteCCL(): Z-slice connected-component labeling with a 2-slice rolling buffer and UnionFind equivalence tracking (Face and FaceEdgeVertex connectivity, optional periodic BCs), replacing random-access BFS/DFS flood-fill on disk-backed data.
  • UnionFind — vector-based disjoint set with union-by-rank and path-halving.
  • SliceBufferedTransfer — type-dispatched Z-slice buffered tuple copy for morphological / neighbor-replacement transfer phases.
  • Extent — region/range math helper (with unit tests).
  • AlignSections OOC path — bulk slice read/write transfer for the align-sections family.

1.8 Core utility bulk-I/O conversions

These live in core utilities and benefit every caller; each is guarded by a runtime storage-type check that preserves the original in-core code path:

  • DataArrayUtilitiesImportFromBinaryFile, AppendData, CopyData, and the mirror swap_ranges ops route through chunked bulk I/O when OOC. (Powers ReadRawBinary and AppendImageGeometry's mirror.)
  • DataGroupUtilities::RemoveInactiveObjects — chunked featureIds renumbering via copyIntoBuffer/copyFromBuffer.
  • ClusteringUtilities::RandomizeFeatureIds — chunked bulk I/O (both overloads; benefits segmentation filters, SharedFeatureFace, MergeTwins).
  • GeometryHelpersFindElementsContainingVert / FindElementNeighbors use 65K-element chunked passes with a current-chunk cache-hit check before falling back to per-element reads.
  • ImageRotationUtilities — Z-slab source cache for nearest-neighbor and a ±2-slice trilinear margin, sliding-window slab updates (memmove + delta reads), and intra-slice parallelism. This is how ApplyTransformationToGeometry and RotateSampleRefFrame get their OOC speedups (no plugin algorithm file changes for ApplyTransformation).
  • TriangleUtilities — bulk-load triangles/labels for winding repair.
  • H5DataStore — streaming row-batch FillOocDataStore replacing full-dataset allocation on import.
  • RectGridGeom / ImageGeom findElementSizes — route through the resolver-aware CreateDataStore (voxel-sizes array can go OOC); the RectGrid inner loop refactored to per-axis precompute + Z-slice copyFromBuffer.

Part 2 — Filter optimizations

Optimization patterns

Every filter optimization is one of these four shapes:

  • (a) Dispatch splitDispatchAlgorithm<…Direct, …Scanline>: an in-core Direct class (unchanged or parallel) and an OOC Scanline class that streams Z-slices / chunks. The original Foo.cpp becomes a thin dispatcher.
  • (b) CCL splitDispatchAlgorithm<…BFS, …CCL> (or an in-file branch): random-access flood-fill in-core, sequential Z-slice connected-component labeling for OOC.
  • (c) Single-implementation bulk I/O — one algorithm that reads/writes in chunks and caches feature/ensemble-level arrays in local std::vectors, with a runtime storage-type check so in-core stays optimal.
  • (d) Slice-buffered / safety — rolling Z-slice buffers via SliceBufferedTransfer, or an OOC-correctness guard (e.g. disabling threading for OOC stores) / progress-and-cancel additions.

Algorithm structure: in-core + out-of-core variants

For dispatch-split filters the original algorithm file is renamed to the in-core variant and the OOC variant is added beside it; the original filename remains as a thin dispatcher:

Filter In-core variant OOC variant
FillBadData FillBadDataBFS FillBadDataCCL
IdentifySample IdentifySampleBFS IdentifySampleCCL
ComputeBoundaryCells …Direct …Scanline
ComputeFeatureNeighbors …Direct …Scanline
ComputeSurfaceFeatures …Direct …Scanline
ComputeSurfaceAreaToVolume …Direct …Scanline
ComputeFeatureSizes …Direct …Scanline
MultiThresholdObjects …Direct …Scanline
DBSCAN …Direct …Scanline
ComputeKMedoids …Direct …Scanline
QuickSurfaceMesh …Direct …Scanline
SurfaceNets …Direct …Scanline
BadDataNeighborOrientationCheck (OA) …Worklist …Scanline
ComputeGBCDPoleFigure (OA) …Direct …Scanline

New shared header IdentifySampleCommon.hpp (SimplnxCore) provides the VectorUnionFind and per-slice functor shared by the BFS/CCL variants; TupleTransfer.hpp gains quickSurfaceTransferBatch / surfaceNetsTransferBatch bulk APIs used by the mesh Scanline variants.

SimplnxCore inventory

Filter Pattern Technique
FillBadData (b) DispatchAlgorithm<FillBadDataBFS, FillBadDataCCL>; CCL streams Z-slices with core UnionFind
IdentifySample (b) DispatchAlgorithm<IdentifySampleBFS, IdentifySampleCCL>; scanline labeling via VectorUnionFind
ScalarSegmentFeatures (b) In-file OOC branch to SegmentFeatures::executeCCL() (Z-slice CCL)
ComputeBoundaryCells (a) Scanline Z-slice rolling-window neighbor reads
ComputeSurfaceFeatures (a) Scanline Z-slice rolling window
ComputeFeatureNeighbors (a) Scanline Z-slice rolling window
ComputeSurfaceAreaToVolume (a) Scanline Z-slice rolling window
ComputeFeatureSizes (a) Direct = tbb parallel Kahan accumulation; Scanline = chunked copyIntoBuffer (256K-tuple)
MultiThresholdObjects (a) Scanline eliminates the O(n) temp result vector
DBSCAN (a) Chunked grid build + on-demand per-cell coordinate reads in canMerge
ComputeKMedoids (a) Chunked findClusters; bounded per-cluster peak memory
QuickSurfaceMesh (a) Scanline drops the O(volume) nodeIds for rolling 2-plane node buffers; quickSurfaceTransferBatch
SurfaceNets (a) Scanline reimplements with a hash-map surface store; surfaceNetsTransferBatch
ComputeFeatureCentroids (c) Plain std::vector accumulators (no DataStore); 64K-tuple chunked featureIds
ComputeFeatureClustering (c) Feature-level array caching; RDF in local vectors
ComputeEuclideanDistMap (c) Bulk-read into local vectors, flood-fill in RAM, bulk-write
ComputeFeaturePhases (c) 65K-tuple chunked featureIds + cellPhases; feature-level vectors replace per-cell map
RequireMinimumSizeFeatures (c) Chunked feature removal + 3-slice rolling slab for assignBadVoxels voting; sparse changed-voxel tracking
CropImageGeometry (c) K=32 Z-slice batched slab I/O (O(n^⅔) working set)
ExtractInternalSurfacesFromTriangleGeometry (c) triMask bitset + sparse triPrefixSum popcount (~6.4× less memory); 65K-element streamed passes
ComputeTriangleAreas (c) Filter-level chunked triangle connectivity + span-bounded vertex loads; parallel compute on local buffers
RegularGridSampleSurfaceMesh (c/d) ZSliceWorker parallel Z-slice rasterize → mutex-guarded bulk copyFromBuffer
ErodeDilateBadData (d) SliceBufferedTransfer per-Z commit + Z-slice neighbor reads
ErodeDilateCoordinationNumber (d) SliceBufferedTransfer per-Z commit
ErodeDilateMask (c/d) Slice-based bulk mask erosion/dilation
ReplaceElementAttributesWithNeighborValues (d) SliceBufferedTransfer per-Z best-neighbor commit
AlignSectionsFeatureCentroid (d) In-file OOC branch findShiftsOoc() with per-Z-slice mask reads
WriteAvizoRectilinearCoordinate / WriteAvizoUniformCoordinate (c) Bulk copyIntoBuffer for coordinate/data output
ComputeArrayStatistics (d) OOC-safety: parallelization disabled when stores are OOC
ReadHDF5Dataset / ReadStlFile (d) Cancel checks + throttled progress

OrientationAnalysis inventory

Filter Pattern Technique
ComputeIPFColors (a) DispatchAlgorithm<…Direct, …Scanline>; Direct keeps parallel in-core, Scanline 65K-tuple chunks + cached crystal structures; color key forwarded to the OOC path
ComputeGBCDPoleFigure (a) Dispatched at the filter level; Scanline caches only the phase-of-interest GBCD slice
BadDataNeighborOrientationCheck (a) DispatchAlgorithm<…Worklist, …Scanline>; bool-mask reads routed through bulk I/O
EBSDSegmentFeatures (b) SegmentFeatures::executeCCL() slice-by-slice, replacing DFS flood-fill
CAxisSegmentFeatures (b) Same CCL architecture as EBSDSegmentFeatures
AlignSectionsMisorientation (c) findShiftsOoc(), 2-slice buffered quats/phases/mask + cached crystal structures
AlignSectionsMutualInformation (c) Per-slice bulk reads of phases/quats/mask + cached crystal structures
NeighborOrientationCorrelation (c/d) 3-slice rolling window via SliceBufferedTransfer; cached crystal structures
ComputeAvgOrientations (c) Chunked featureIds/phases/quats; cached crystal structures + avgQuats; bulk write
ComputeFeatureReferenceMisorientations (c) Chunked cell-level arrays; cached crystal structures, avgQuats, center quats
ComputeKernelAvgMisorientations (c) Per-Z-plane [plane−kZ, plane+kZ] slab reads; cached crystal structures
ComputeAvgCAxes (c) 4096-tuple chunked reads; feature-level avgCAxes + crystal structures cached
ComputeCAxisLocations (c) 64K-tuple chunked read/process/write; cached crystal structures
ComputeFeatureReferenceCAxisMisorientations (c) Z-slice buffered cell-level I/O; cached crystalStructures + avgCAxes
ComputeFeatureNeighborCAxisMisalignments (c) Bulk-read feature-level arrays; buffered output
ComputeTwinBoundaries (c) Bulk-read all face/feature/ensemble arrays into local vectors
MergeTwins (c) Chunked voxel parent-ID fill + assignment; feature-level parentIds cached
ConvertOrientations (c) Macro-generated convertors process each range in 4096-tuple chunks (bulk read→convert→bulk write)
RotateEulerRefFrame (c) 64K-tuple in-place read-modify-write chunks
ComputeGBCD (c) Feature-level caching; chunked 50K-triangle reads; GBCD accumulated locally then copyFromBuffer
ComputeGBCDMetricBased (c) Per-chunk sequential area accumulation (no O(n) triIncluded); feature-level caching; raw-pointer parallel selector
ComputeGBPDMetricBased (c) Caches feature eulers/phases + ensemble crystal structures; chunked triangle reads (distinct filter from GBCD MetricBased)
WriteGBCDGMTFile (c) Caches phase-of-interest GBCD slice; crystal structures cached
WriteGBCDTriangleData (c) 8K-triangle chunked reads; feature-level Euler cache; output buffered via fmt::memory_buffer
ReadAngData / ReadCtfData (c) Bulk copyFromBuffer for all cell arrays; chunked Euler interleave; in-place phase validation
ReadH5Ebsd (c) copyFromBuffer in CopyData template, phase copy, Euler interleave
ReadH5EspritData (c) Bulk copyFromBuffer from raw HDF5 reader buffers
WritePoleFigure (c) Per-phase chunked input reads with bounded buffers; bulk copyFromBuffer for intensity/image outputs

Cancel + progress

In-core and OOC variants gained m_ShouldCancel checks at the top of major loops and ThrottledMessenger-based progress reporting with per-phase messages and percent-complete.


Part 3 — Performance results

All benchmarks use arm64 Release builds. OOC measurements force the disk-backed path through DataStorageMode::ForceOutOfCore or ForceOocAlgorithmGuard.

CRITICAL filter optimizations (existing unit-test data, filter.execute() only)

Filter IC Before (s) IC After (s) IC Speedup OOC Before (s) OOC After (s) OOC Speedup
ComputeShapesTriangleGeom 0.012920 0.012610 1.02x 0.012910 0.012560 1.03x
RemoveFlaggedFeatures 0.000890 0.000990 0.90x 0.003230 0.003450 0.94x
ComputeKMeans 0.000980 0.000970 1.01x 0.020480 0.020290 1.01x
ComputeArrayStatistics 0.000790 0.000700 1.13x 0.004800 0.004670 1.03x
ComputeArrayHistogramByFeature 0.000710 0.000850 0.84x 0.003890 0.004020 0.97x
ResampleRectGridToImageGeom 0.000890 0.000780 1.14x 0.178960 0.009790 18.28x
ResampleImageGeom 0.002170 0.000940 2.31x 1.658850 0.012980 127.80x
UncertainRegularGridSampleSurfaceMesh 0.003190 0.001850 1.72x 0.487270 0.003690 132.05x
ArrayCalculator 0.000240 0.000240 1.00x 0.000250 0.000230 1.09x
FlyingEdges3D 0.000420 ~0.000100 ~4.20x 0.004830 0.000150 32.20x
ConvertOrientationsToVertexGeometry 0.016570 0.014700 1.13x 0.311490 0.020160 15.45x
ExtractVertexGeometry 0.001500 0.000690 2.17x 0.601910 0.001680 358.28x
ComputeVertexToTriangleDistances 0.000600 0.000750 0.80x 0.000620 0.002030 0.31x
WriteAbaqusHexahedron 0.018990 0.016520 1.15x 4.294920 0.201020 21.37x
WriteStlFile 0.000790 0.000840 0.94x 0.000720 0.001440 0.50x
VerifyTriangleWinding 0.003020 0.003250 0.93x 0.003010 0.003490 0.86x
CreateAMScanPaths 0.020630 0.002070 9.97x 0.020620 0.002180 9.46x

Small existing datasets do not expose every asymptotic win: mesh bucketing/pruning and bounded-memory streaming can add fixed overhead at this scale. All 17 filters passed their affected suites in both configurations; the table reports measured values rather than hiding those small-test regressions.

Large synthetic filter benchmarks (filter.execute() only)

Tier Filter IC Before (s) IC After (s) IC Speedup OOC Before (s) OOC After (s) OOC Speedup
HIGH ComputeArrayHistogram 0.088506 0.053227 1.66x 487.917981 0.814006 599x
HIGH ComputeFeatureBounds 0.053695 0.002626 20.45x 65.637232 0.186534 352x
HIGH ComputeFeatureRect 0.071993 0.017759 4.05x 715.698738 0.090509 7,907x
HIGH ExtractFeatureBoundaries2D 0.028188 0.011352 2.48x 63.905878 0.024392 2,620x
HIGH PartitionGeometry 0.005694 0.001530 3.72x 107.960858 0.054839 1,969x
HIGH ReadChannel5Data 0.408973 0.345354 1.18x >300 0.368083 >815x
HIGH CreateColorMap 0.029480 0.010684 2.76x >120 0.182033 >659x
HIGH ComputeBoundingBoxStats 0.019897 0.014888 1.34x >60 1.075362 >55.8x
HIGH CombineAttributeArrays 0.062550 0.056931 1.10x >60 0.391489 >153x
HIGH ComputeCoordinateThreshold 0.090332 0.000675 133.8x 7.620420 0.090790 83.9x
HIGH ComputeDifferencesMap 0.022731 0.002642 8.60x >60 0.184405 >325x
HIGH ComputeLargestCrossSections (XY) 0.029501 0.026580 1.11x 24.694420 0.032494 760x
HIGH ComputeLargestCrossSections (XZ) 0.024886 0.006156 4.04x 24.816181 0.107269 231x
HIGH ComputeLargestCrossSections (YZ) 0.029908 0.020359 1.47x 26.939749 0.302016 89.2x
HIGH ComputeVectorColors 0.116860 0.062063 1.88x >60 0.248597 >241x
HIGH ComputeFZQuaternions 0.051898 0.038698 1.34x >60 0.595947 >100x
HIGH ComputeMisorientations 0.080476 0.052000 1.55x >60 0.062403 >961x
HIGH ComputeFeaturePhasesBinary 0.027152 0.007749 3.50x >60 0.009101 >6,592x
HIGH ConditionalSetValue 0.009228 0.001509 6.12x >60 0.165667 >362x
HIGH ConvertColorToGrayScale 0.004248 0.001848 2.30x >60 0.101111 >593x
HIGH ConvertQuaternion 0.008270 0.001548 5.34x >60 0.127112 >472x
HIGH ChangeAngleRepresentation 0.002260 0.001425 1.59x >60 0.052921 >1,133x
HIGH ComputeCoordinatesImageGeom 0.006337 0.001267 5.00x >60 0.019143 >3,134x
HIGH AddBadData 0.092763 0.009174 10.11x 49.366814 0.183687 268.76x
HIGH WriteStatsGenOdfAngleFile 0.022509 0.004826 4.66x >60 0.227642 >263x
HIGH WriteVtkStructuredPoints 0.026717 0.015289 1.75x >60 0.016252 >3,691x
HIGH SplitDataArrayByComponent 0.031228 0.002297 13.60x >60 0.198318 >302x
HIGH SplitDataArrayByTuple 0.055733 0.001667 33.43x >60 0.102487 >585x
MEDIUM ComputeMomentInvariants2D 0.001834 0.001560 1.18x 13.974384 0.012929 1,080.86x
MEDIUM CreateFeatureArrayFromElementArray 0.204728 0.026567 7.71x >60 0.251625 >238.45x
MEDIUM ExtractComponentAsArray 0.055706 0.031082 1.79x >60 0.192867 >311.10x
MEDIUM ConvertData 0.005581 0.005187 1.08x >60 0.206812 >290.12x
MEDIUM GroupMicroTextureRegions 0.015163 0.003478 4.36x >60 0.101423 >591.58x
MEDIUM ComputeBoundaryElementFractions 0.013681 0.008264 1.66x 51.947201 0.159685 325.31x
MEDIUM ComputeQuaternionConjugate 0.008203 0.007182 1.14x >60 0.128279 >467.73x
MEDIUM ReadBinaryCTNorthstar 0.018764 0.007421 2.53x >60 0.036908 >1,625.66x
MEDIUM ReadCSVFile 1.126726 1.101959 1.02x >60 1.177329 >50.96x
MEDIUM ReadVtkStructuredPoints 0.300159 0.265677 1.13x >60 0.298817 >200.79x
MEDIUM WriteINLFile 7.112353 7.050428 1.01x >60 7.331228 >8.18x
MEDIUM WriteLosAlamosFFT 1.727152 1.578432 1.09x >60 2.181956 >27.50x

HIGH and MEDIUM benchmarks use 8,000,000 cells (200³) except the 2D-only tests (2048²), the generated Channel5 reader dataset (8,000 × 1,000), and CSV's 8,000,000 rows. Figures are medians of five execute-only runs for the MEDIUM batches and repeated runs for HIGH where practical; > values are conservative timeout bounds. Every listed filter passed its normal affected suite and hidden large benchmark in both configurations.

Additional 200³ execute-only OOC benchmarks

Group Filter Before (s) After (s) Speedup
Groups B–E ComputeBoundaryCells 6.69 0.25 27x
Groups B–E ComputeSurfaceFeatures 4.01 0.28 14x
Groups B–E ComputeFeatureNeighbors 8.93 0.81 11x
Groups B–E ComputeSurfaceAreaToVolume 8.59 0.24 36x
Groups B–E BadDataNeighborOrientationCheck 97.1 5.25 18x
Groups B–E ErodeDilateBadData 25.09 3.80 7x
Groups B–E ErodeDilateCoordinationNumber 12.43 2.30 5x
Groups B–E ErodeDilateMask 6.43 0.40 16x
Groups B–E ReplaceElementAttrsWithNeighborValues 6.05 4.00 1.5x
Groups B–E NeighborOrientationCorrelation 67.94 5.70 12x
Groups B–E ScalarSegmentFeatures 708.3 1.77 400x
Groups B–E EBSDSegmentFeatures 972.6 2.10 463x
Groups B–E CAxisSegmentFeatures 824.1 1.39 593x
Groups B–E FillBadData 8.6 2.26 4x
Groups B–E IdentifySample 825.0 0.27 3056x
Groups B–E AlignSectionsMisorientation 32.89 0.80 41x
Groups B–E AlignSectionsMutualInformation 15.61 0.81 19x
Groups B–E AlignSectionsFeatureCentroid 8.41 0.39 22x
Groups B–E AlignSectionsListFilter 7.50 0.39 19x
Pipeline-critical ComputeFeatureCentroids 39.700 0.025 1,589x
Pipeline-critical RequireMinimumSizeFeatures 20.200 0.210 96x
Pipeline-critical ComputeIPFColors 1.940 0.090 21.5x
Pipeline-critical ComputeFeatureSizes 0.813 0.028 29x
Pipeline-critical ComputeFeatureReferenceMisorientations (AvgOri) 0.106 0.001 106x
Pipeline-critical ComputeFeatureReferenceMisorientations (EuclDist) 0.136 0.001 136x

Full-test wall-clock OOC benchmarks

Group Test Before (s) After (s) Speedup
Mesh generation QuickSurfaceMesh: Base 11.30 0.19 59x
Mesh generation QuickSurfaceMesh: Winding 22.70 0.22 103x
Mesh generation QuickSurfaceMesh: Problem Voxels 11.18 0.19 59x
Mesh generation QuickSurfaceMesh: Winding+PV 21.96 0.22 100x
Mesh generation SurfaceNets: Default 176 2.40 73x
Mesh generation SurfaceNets: Smoothing 224 2.62 85x
Mesh generation SurfaceNets: Winding 515 2.86 180x
Mesh generation SurfaceNets: Winding Smoothing 416 3.22 129x
OrientationAnalysis ComputeFeatureReferenceCAxisMisorientations 196 5.4 36x
OrientationAnalysis ComputeEuclideanDistMap 116 1.1 105x
GBCD ComputeGBCDPoleFigure 833 (fail) 2.4 350x
GBCD ComputeGBCD 1500 (timeout) ~10 150x
GBCD WriteGBCDGMTFile 162 (fail) 6.0 27x
GBCD ComputeGBCDMetricBased 38.1 28.9 1.3x
GBCD WriteGBCDTriangleData 23.5 19.2 1.2x
HDF5 / pole figure WritePoleFigure (3 tests) 4500 (timeout) 11.7 385x
HDF5 / pole figure ReadH5EspritData (3 tests) 2060 (timeout) 6.8 303x
HDF5 / pole figure ReadHDF5Dataset 1500 (timeout) 6.7 224x
Additional ReadRawBinary (Case1) 1076 29 37x
Additional ComputeGBCDPoleFigure 853 0.9 948x
Additional DBSCAN 3D 653 12 54x
Additional AlignSectionsMisorientation Pipeline 635 5.9 107x
Additional ReadH5Ebsd 463 2.1 220x
Additional ReadCtfData 231 0.25 924x
Additional AppendImageGeometry 469 113 4.2x
Additional ComputeFeatureClustering 203 77 2.6x
Additional ComputeTwinBoundaries 179 44 4x
Additional MergeTwins 67 1.8 37x
Additional ComputeKMedoids 74 13 5.7x
Additional CropImageGeometry (X) 27 2.6 10x
Additional WriteAvizoRectilinear 22.8 2.3 10x
Additional WriteAvizoUniform 22.3 2.0 11x

Geometry / Mesh / Phase Filters

Filter Before After Note
ApplyTransformationToGeometry (trilinear, CT_align 1.97 B-voxel) 133s 20s ~6.6x; via ImageRotationUtilities slab cache
ComputeTriangleAreas (CT_align mesh) 26s <1s ~26x
ComputeFeaturePhases (748,800 cells, disk-backed) 11.7s 35ms feature-level vectors + chunked reads
ComputeGBPDMetricBased 20.5s 6.3s ~3.3x; feature/ensemble caching
ExtractInternalSurfacesFromTriangleGeometry ~6.4x less triangle-side memory (bitset + popcount)

Part 4 — Test infrastructure

  • Comparison functions rewritten for chunked bulk I/O (UnitTestCommon.hpp): CompareDataArrays, CompareDataArraysByComponent, CompareArrays, CompareFloatArraysWithNans stream both arrays in 40K-element chunks via copyIntoBuffer instead of per-element operator[] (per-element access on OOC arrays is pathologically slow).
  • New store-type helpers: ExpectedStoreType() (derives the expected StoreType from the active DataStorageMode + whether an OOC manager is registered) and RequireExpectedStoreType().
  • PreferencesSentinel takes a DataStorageMode and restores the original preferences on destruction.
  • TestFileSentinel reference-counts archive extraction via per-process holder files, so parallel test runs don't delete shared decompressed data prematurely.
  • LoadDataStructure test helper calls DREAM3D::LoadDataStructure(path) directly.
  • New SegmentFeaturesTestUtils.hpp (~638 lines): shared builders/verifiers for the SegmentFeatures family.
  • Dual-path testing: ForceOocAlgorithmGuard + GENERATE(from_range(k_ForceOocTestValues)) runs each case in both in-core and forced-OOC modes — adopted by 23 plugin test files (16 SimplnxCore, 7 OrientationAnalysis).
  • 8 new top-level tests in test/: DataStoreFormatResolverTest, DataStorageModeMigrationTest, Dream3dLoadingApiTest, EmptyStringStoreTest, ExtentTest, MemoryBudgetManagerTest, MemorySafetyTest, IParallelAlgorithmTest.
  • RotateSampleRefFrame / RotateEulerRefFrame test paths use slab/chunked bulk I/O.

Part 5 — Build system

  • OOC is a runtime capability — it is selected entirely by the registered IO manager; there is no compile-time OOC switch. cmake/SimplnxConfig.hpp.in is a generated PUBLIC, ODR-safe config header.
  • SIMPLNX_TEST_ALGORITHM_PATH cache option (default 0): 0=Both, 1=OOC-only, 2=InCore-only; plumbed into every plugin test as a compile definition (cmake/Plugin.cmake). An in-core build can validate both algorithm paths (forcing the Scanline path against in-core data still runs correctly and fast).
  • SIMPLNX_UNIT_TEST_TARGETS global propertycreate_simplnx_plugin_unit_test records each test target so consumer (add_subdirectory) builds can attach extra sources/settings.
  • zlib as a direct vcpkg dependency — the OOC layer compiled into consumers uses it directly for parallel deflate-chunk decompression off the global HDF5 mutex (and UnitTestCommon uses it for the in-house tar.gz extractor).
  • /bigobj for the MSVC build of Dream3dIO.cpp (exceeds the COMDAT limit / C1128 in Debug).
  • New core headers/sources registered in CMake (Extent, EmptyStringStore, IDataStoreFormatResolver, InMemoryFormatResolver, MemoryBudgetManager, AlgorithmDispatch, SliceBufferedTransfer, UnionFind, IdentifySampleCommon).

Part 6 — Documentation

  • 48 filter docs updated under src/Plugins/*/docs/ (27 SimplnxCore, 21 OrientationAnalysis; ~+1,177 lines), each adding an ## Algorithm section with ### Performance and paired In-Core / Out-of-Core subsections that explain the dual implementation, memory footprint, and chunk/slab streaming strategy. No docs deleted.

Part 7 — Test data archives

Three download_test_data() entries added:

  • fill_bad_data_exemplars.tar.gz (SimplnxCore)
  • identify_sample_exemplars.tar.gz (SimplnxCore)
  • segment_features_exemplars.tar.gz (referenced by both SimplnxCore and OrientationAnalysis)

No existing archive entries were removed.


Related PR

  • BlueQuartzSoftware/DREAM3DNX#1121 — DREAM3D-NX OOC visualization architecture; consumes the runtime resolver/loader APIs added here and depends on this PR merging first.

Test Plan

  • In-core build (SIMPLNX_TEST_ALGORITHM_PATH=2) passes
  • Out-of-core build (SIMPLNX_TEST_ALGORITHM_PATH=1) passes
  • Both algorithm paths (SIMPLNX_TEST_ALGORITHM_PATH=0) pass
  • All optimized filters produce identical results on both algorithm paths
  • In-core performance verified: no regression on the core-utility changes (CopyData, AppendData, mirror swaps)

@joeykleingers
joeykleingers force-pushed the worktree-ooc-architecture-rewrite branch from b4ef97f to 99b49ed Compare March 24, 2026 18:13
@joeykleingers
joeykleingers force-pushed the worktree-ooc-architecture-rewrite branch 2 times, most recently from b4ef97f to bb09048 Compare March 24, 2026 18:51
@joeykleingers
joeykleingers force-pushed the worktree-ooc-architecture-rewrite branch 4 times, most recently from 102c436 to b4c1358 Compare April 2, 2026 00:55
@joeykleingers joeykleingers changed the title WIP: OOC architecture rewrite — new bulk I/O API, SimplnxOoc plugin, and filter optimizations ENH: OOC architecture rewrite — new bulk I/O API and infrastructure Apr 2, 2026
@joeykleingers
joeykleingers force-pushed the worktree-ooc-architecture-rewrite branch 6 times, most recently from 2bd614a to 110c054 Compare April 8, 2026 17:41
@joeykleingers joeykleingers changed the title ENH: OOC architecture rewrite — new bulk I/O API and infrastructure WIP: ENH: OOC architecture rewrite — new bulk I/O API and infrastructure Apr 8, 2026
@joeykleingers
joeykleingers force-pushed the worktree-ooc-architecture-rewrite branch 4 times, most recently from 35aecd0 to 3a88bbf Compare April 16, 2026 13:03
@joeykleingers
joeykleingers force-pushed the worktree-ooc-architecture-rewrite branch from bdfed87 to 6fbfc8d Compare April 27, 2026 18:18
@joeykleingers
joeykleingers force-pushed the worktree-ooc-architecture-rewrite branch from ec3a5e7 to 3c8903e Compare June 12, 2026 15:32
@joeykleingers
joeykleingers force-pushed the worktree-ooc-architecture-rewrite branch 2 times, most recently from ed64934 to a56f168 Compare June 25, 2026 17:25
@joeykleingers
joeykleingers force-pushed the worktree-ooc-architecture-rewrite branch 3 times, most recently from c6bb7fe to ee6b370 Compare July 13, 2026 18:29
@joeykleingers
joeykleingers force-pushed the worktree-ooc-architecture-rewrite branch 2 times, most recently from 5a8c281 to b61d3fd Compare July 15, 2026 17:22
@joeykleingers
joeykleingers force-pushed the worktree-ooc-architecture-rewrite branch 5 times, most recently from 9e11033 to 03ac304 Compare July 30, 2026 14:22
@joeykleingers
joeykleingers marked this pull request as ready for review July 30, 2026 14:27
@joeykleingers
joeykleingers force-pushed the worktree-ooc-architecture-rewrite branch 7 times, most recently from 89e66d2 to f106b35 Compare August 10, 2026 20:33
Add storage-neutral backing-store selection and bulk I/O contracts so core
filters can choose between direct in-memory and bounded-memory algorithms at
runtime. Split affected SimplnxCore and OrientationAnalysis implementations
into direct, scanline, BFS, CCL, and worklist paths without depending on a
concrete out-of-core backend. Expand algorithm-path tests, storage-policy
coverage, documentation, and build wiring.

663 files changed, +84,941 / -20,875 lines.

================================================================================
1. Define storage policy and bulk data-store interfaces
================================================================================
Files: src/simplnx/Core/{Application,Preferences}.{cpp,hpp},
       src/simplnx/DataStructure/{AbstractDataStore,DataStore,EmptyDataStore,
       IDataStore}.{hpp}, src/simplnx/DataStructure/IO/{Generic,HDF5}/...,
       src/simplnx/Filter/Actions/...

Introduce Adaptive, ForceInCore, and ForceOutOfCore storage modes with migration
from the legacy preference keys. Let each DataStructure resolve registered
storage formats through a storage-neutral IDataStoreFormatResolver, and expose
format registration, base-directory, and manager lookup behavior through the
application and I/O collection APIs.

Add contiguous copy and N-dimensional extent operations to the common data-store
contract, with in-memory, empty, HDF5, string, and NeighborList implementations.
Route array and geometry creation actions through the selected format so storage
intent applies consistently to newly allocated data.

================================================================================
2. Add runtime dispatch and bounded-memory infrastructure
================================================================================
Files: docs/AlgorithmDispatch.md, src/simplnx/Common/Extent.hpp,
       src/simplnx/Utilities/{AlgorithmDispatch,BoundedRecordPageCache,
       ExternalEquivalence,InMemoryTemporaryRecordStore,MemoryBudgetManager,
       SliceBufferedTransfer,UnionFind}.*,
       src/simplnx/DataStructure/IO/Generic/{IExternalSort,
       ITemporaryRecordStore}.hpp

Extend algorithm dispatch to mixed IArray targets, including NeighborLists, and
select implementations from storage residency plus explicit test overrides.
Record the algorithm/store combination that actually executes so tests can
witness dispatch rather than infer it from configuration.

Add reusable extent, temporary-record, external-equivalence, page-cache,
memory-budget, slice-transfer, and union-find primitives for algorithms that
must bound resident memory and preserve cancellation and error propagation.
Update shared data-array, segmentation, geometry, clustering, meshing, and file
utilities to use the common bulk-access contracts.

================================================================================
3. Provide storage-aware SimplnxCore algorithm paths
================================================================================
Files: src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/...,
       src/Plugins/SimplnxCore/src/SimplnxCore/Filters/...,
       src/Plugins/SimplnxCore/src/SimplnxCore/utils/VtkUtilities.hpp

Route array statistics and histograms, clustering, thresholding, tuple transfer,
feature operations, partitioning, silhouette generation, and surface meshing
through direct or scanline implementations selected from the participating
stores. This includes dedicated paths for DBSCAN, MultiThresholdObjects,
QuickSurfaceMesh, RemoveFlaggedFeatures, SurfaceNets, and related filters.

Add BFS and connected-component alternatives for FillBadData and IdentifySample,
and move affected readers, writers, segmentation utilities, and geometry
algorithms onto buffered or bulk data access so the scanline paths avoid
full-volume materialization.

================================================================================
4. Provide storage-aware OrientationAnalysis algorithm paths
================================================================================
Files: src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters/
       Algorithms/..., src/Plugins/OrientationAnalysis/src/
       OrientationAnalysis/Filters/...,
       src/Plugins/OrientationAnalysis/src/OrientationAnalysis/utilities/...

Add direct and scanline implementations for GBCD pole figures, IPF colors,
kernel average misorientations, quaternion conjugation, and other
orientation-array operations. Add scanline and worklist processing for bad-data
neighbor correction, and adapt feature segmentation, section alignment,
crystallographic calculations, EBSD readers, and export algorithms to the
storage-neutral access layer.

================================================================================
5. Expand path-specific tests, documentation, and build integration
================================================================================
Files: test/..., test/UnitTestCommon/..., src/Plugins/SimplnxCore/test/...,
       src/Plugins/OrientationAnalysis/test/...,
       src/Plugins/{SimplnxCore,OrientationAnalysis}/docs/...,
       CMakeLists.txt, cmake/..., vcpkg.json

Add AlgorithmTestScope and dispatch-state support that run explicitly selected
in-core and out-of-core algorithm implementations on in-memory stores and verify
the implementation that executed. Extend filter coverage for both paths and add
focused tests for extents, storage-mode migration, format resolution, temporary
records, memory budgets, memory safety, and DREAM3D/HDF5 loading behavior.

Register the new sources, test modes, exported configuration, and dependencies,
and document the storage-sensitive behavior of the migrated filters and dispatch
APIs.

================================================================================
Verification
================================================================================

- clang-format verified the three conflict-resolved C++ files using the
  repository-root .clang-format.
- git diff --check passed for the rebased tree.
- CreateFeatureArrayFromElementArray passed 12/12 in-core tests and 12/12
  OOC-build tests, including all upstream AF-1 through AF-9 V&V cases.
- The private 200x200x200 disk-backed OOC test passed 3,495 assertions.
- The in-core all-target build passed. The OOC simplnx_test target compiled
  successfully against the live SimplnxOoc backend.
- Dream3dLoadingApi and ComputeCAxisLocations passed 15/15 in-core tests
  and 19/19 OOC-build tests, including the private disk-backed cases.

Signed-off-by: Joey Kleingers <joey.kleingers@bluequartz.net>
@joeykleingers
joeykleingers force-pushed the worktree-ooc-architecture-rewrite branch from f106b35 to d54fab8 Compare August 11, 2026 14:38
* Allocate bounded OIM and OINA scratch pages on the heap to keep them resident without exhausting the default Windows thread stack.
* Apply the clang-format 15 layout required by CI.

Signed-off-by: Joey Kleingers <joey.kleingers@bluequartz.net>
Signed-off-by: Joey Kleingers <joey.kleingers@bluequartz.net>
Serialize HDF5 C API access within file, group, dataset, object,
attribute, and DREAM3D reader leaf operations through the shared
non-recursive ApiLock. Keep nested self-locking calls outside leaf critical
sections and close transient HDF5 identifiers under the same lock. 9 files
changed, +966 / -479 lines.

================================================================================
1. HDF5 leaf-level serialization
================================================================================
Files: H5Support.hpp, DatasetIO.cpp, FileIO.cpp, GroupIO.cpp, ObjectIO.cpp,
ObjectIO.hpp

Wrap file, group, dataset, dataspace, property-list, object, and attribute
HDF5 calls with the process-wide API mutex. Resolve lazy-opening and other
self-locking operations before entering leaf critical sections to preserve
the public-locks/private-unlocked contract and avoid recursive deadlocks.
Keep related HDF5 resource lifetimes and error-handler changes within the
serialized scopes while translating errors after releasing the lock.

================================================================================
2. DREAM3D attribute import
================================================================================
Files: Dream3dIO.cpp, ObjectIO.cpp, ObjectIO.hpp

Inspect legacy attribute types under the shared HDF5 lock, then release it
before dispatching to self-locking string and vector readers. Serialize
attribute enumeration, type and dataspace queries, reads, writes, deletes,
and cleanup while preserving typed DataArray conversion behavior.

================================================================================
3. Concurrent HDF5 regression coverage
================================================================================
Files: test/CMakeLists.txt, test/H5IOTest.cpp

Add a multithreaded regression test that repeatedly opens one fixture file,
queries groups and datasets, and validates full-array and hyperslab reads.
Exercise concurrent open, query, read, close, and destructor paths to detect
HDF5 races, data corruption, and non-recursive-lock deadlocks.

Verification
============
clang-format 15.0.7 --dry-run --Werror passed for all changed C++ files.
git diff --cached --check passed. Builds and tests were not rerun as part of
this history-only squash.
* Remove progress and status emissions introduced by storage-neutral paths
* Preserve pre-existing diagnostics and output behavior

Signed-off-by: Joey Kleingers <joey.kleingers@gmail.com>
@joeykleingers
joeykleingers force-pushed the worktree-ooc-architecture-rewrite branch from ec2e32c to 09d552a Compare August 13, 2026 18:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant