Skip to content

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

Draft
joeykleingers wants to merge 1 commit into
BlueQuartzSoftware:developfrom
joeykleingers:worktree-ooc-architecture-rewrite
Draft

ENH: Out-of-core architecture rewrite and filter optimizations#1568
joeykleingers wants to merge 1 commit 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 marked this pull request as ready for review April 28, 2026 00:08
@joeykleingers joeykleingers changed the title WIP: ENH: OOC architecture rewrite — new bulk I/O API and infrastructure ENH: OOC architecture rewrite — new bulk I/O API and infrastructure Apr 28, 2026
@joeykleingers
joeykleingers force-pushed the worktree-ooc-architecture-rewrite branch from 2095f32 to 65c4ba8 Compare May 5, 2026 13:30
@joeykleingers
joeykleingers force-pushed the worktree-ooc-architecture-rewrite branch from 65c4ba8 to 653cf62 Compare May 12, 2026 15:03
@joeykleingers
joeykleingers force-pushed the worktree-ooc-architecture-rewrite branch 3 times, most recently from 4834fa1 to d4f634b Compare June 1, 2026 15:13
@joeykleingers
joeykleingers force-pushed the worktree-ooc-architecture-rewrite branch from d4f634b to c04347c Compare June 5, 2026 15:58
@joeykleingers joeykleingers changed the title ENH: OOC architecture rewrite — new bulk I/O API and infrastructure ENH: Out-of-core architecture rewrite and filter optimizations Jun 5, 2026
@joeykleingers
joeykleingers marked this pull request as draft June 11, 2026 14:25
@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 from 8f3727e to 5a8c281 Compare July 15, 2026 15:18
Adds storage-neutral APIs for choosing, creating, reading, writing, and
recovering data stores at runtime. Equips SimplnxCore and
OrientationAnalysis filters with bounded direct and scanline execution paths,
plus memory budgeting, allocation safeguards, bulk transfer primitives, and
DREAM3D/HDF5 integration. 561 files changed, +67105 / -18693 lines.

================================================================================
1. Storage-neutral data stores and runtime format resolution
================================================================================
Files: src/simplnx/DataStructure, src/simplnx/DataStructure/IO,
src/simplnx/Core, src/simplnx/Filter/Actions

Extend datastore contracts with bulk, chunk, extent, planned-format, and
recovery metadata operations while retaining in-memory implementations. Add
runtime format resolvers and DataIOCollection hooks so applications can supply
concrete storage backends without coupling simplnx to them. Carry selected
formats through array, neighbor-list, geometry, string-store, HDF5, and
DREAM3D creation and recovery paths, including metadata-only placeholder
stores.

================================================================================
2. Bounded-memory execution infrastructure
================================================================================
Files: src/simplnx/Filter/IFilter.cpp, src/simplnx/Utilities,
src/simplnx/Common/Extent.hpp

Add machine-aware shared memory budgeting, defensive preflight and execution
allocation handling, extent-based access, slice-buffered transfers, union-find
support, and storage-aware algorithm dispatch. Update array, geometry,
clustering, meshing, parsing, and parallel utilities to use batched transfers
and bounded temporary storage while preserving cancellation and error
reporting.

================================================================================
3. Direct and scanline filter algorithms
================================================================================
Files: src/Plugins/SimplnxCore/src/SimplnxCore/Filters,
src/Plugins/OrientationAnalysis/src/OrientationAnalysis/Filters

Split storage-sensitive filters into direct and scanline implementations and
select the appropriate path from runtime storage characteristics. Add bounded,
chunk-sequential processing for segmentation, statistics, feature analysis,
meshing, transforms, sampling, file output, and orientation workflows while
retaining optimized in-memory paths.

================================================================================
4. Tests, documentation, and build integration
================================================================================
Files: src/Plugins/*/test, src/Plugins/*/docs, test, cmake, vcpkg.json

Register both algorithm paths, expand exemplar and regression coverage for
storage formats, migration, memory safety, bulk I/O, and filter equivalence,
and document affected filters. Update CMake, test utilities, plugin metadata,
and dependencies for the storage-neutral execution model.

Verification: The squashed commit tree is checked against the original range
tip. No additional build or test run is performed for this history-only
rewrite.
@joeykleingers
joeykleingers force-pushed the worktree-ooc-architecture-rewrite branch from 5a8c281 to b61d3fd Compare July 15, 2026 17:22
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