Skip to content

ENH: Add ASV-over-native-ITK performance benchmark harness#111

Merged
hjmjohnson merged 2 commits intoInsightSoftwareConsortium:masterfrom
hjmjohnson:asv-native-harness
Apr 21, 2026
Merged

ENH: Add ASV-over-native-ITK performance benchmark harness#111
hjmjohnson merged 2 commits intoInsightSoftwareConsortium:masterfrom
hjmjohnson:asv-native-harness

Conversation

@hjmjohnson
Copy link
Copy Markdown
Member

@hjmjohnson hjmjohnson commented Apr 19, 2026

Adds an airspeed-velocity (asv) harness that drives the existing native C++ *Benchmark executables for longitudinal performance-regression tracking. Intended for use from ITK CI — companion change to InsightSoftwareConsortium/ITK#612 (replacing the retired Azure macos-11 job).

Also hardens CopyIterationBenchmark against a SIGSEGV exposed by ITK commit 1d87efa52, so the benchmark runs cleanly across the entire ITK v5.3 → current main range.

Architecture (ASV harness)
  • environment_type: "existing" keeps ASV out of the ITK build loop. The consuming CI workflow builds ITK (base + head) and points the shim at each build via ITK_BENCHMARK_BIN.
  • Each ASV benchmark is a track_* returning seconds parsed from the jsonxx output of HighPriorityRealTimeProbesCollector, bypassing ASV's wall-clock instrumentation of the Python callable.
  • repo: "itk-repo" — the caller places a symlink named itk-repo at the harness root pointing at the ITK clone. This lets ASV validate --set-commit-hash values against git, so result labels track ITK history rather than this repository's history.
Contents
  • asv.conf.json — ASV configuration
  • python/itk_perf_shim/ — subprocess shim parsing jsonxx timings
  • benchmarks/{core,filtering,segmentation}.py — 11 prototype benchmarks
  • README-asv.md — design notes and smoke-test recipe (validated)
  • examples/Core/itkCopyIterationBenchmark.cxx — harden VectorImage pixel construction
CopyIterationBenchmark hardening

Previously, CreateAndInitializeImage seeded pixels via it.Set(static_cast<PixelType>(count)). For scalar / FixedArray pixels this is a value cast, but for VectorImage — whose PixelType is itk::VariableLengthVector<T> — it invokes the VLV length constructor: VLV<float>(count) yields a vector of size count, not a 3-component vector with value count. The first iteration produces a length-0 VLV; ITK's DefaultVectorPixelAccessor::Set then copies NumberOfComponentsPerPixel elements from that source's internal data pointer, which is nullptr on ITK ≥ 1d87efa52 → SIGSEGV.

This commit introduces a PixelFiller<T> trait specialized for VariableLengthVector<T> that constructs a correctly-sized VLV and Fills it with count. The scalar / FixedArray path is unchanged. The benchmark now produces well-defined output across the full ITK bisect range regardless of the internal representation of VariableLengthVector(0).

A companion ITK-side fix is proposed in InsightSoftwareConsortium/ITK#6087 — defensive null-guard in the pixel accessor so any other caller that triggers the same class of bug no longer crashes.

Scope

Harness covers Core (2), Filtering (5), and Segmentation (4) benchmarks — all smoke-validated end-to-end. Registration and Resample benchmarks are intentionally omitted; their C++ sources hit unrelated ITK-level build issues (NrrdIO airFloatQNaN link, Resample -Wmaybe-uninitialized) that are outside the scope of this PR. Adding them later is a small registry + benchmarks/ edit.

Relation to the prior wasm-asv effort

Supersedes the stalled wasm-asv direction on @thewtex's fork (last touched 2024-05-29), which introduced a pnpm+emscripten+wasi dependency surface inappropriate for a pure regression-detection harness.

Smoke-test sample output

asv compare against two labeled runs of the same binary (same-machine jitter only):

| Before [c6b6b608] | After [3a8d2fa6] | Ratio | Benchmark                                         |
|-------------------|------------------|-------|---------------------------------------------------|
| 10.5ms            | 10.2ms           |  0.98 | core.CoreSuite.track_copy_iteration               |
| 4.98ms            | 4.61ms           |  0.93 | core.CoreSuite.track_vector_iteration             |
| 5.39ms            | 5.09ms           |  0.94 | filtering.FilteringSuite.track_binary_add         |
| 48.3ms            | 47.3ms           |  0.98 | filtering.FilteringSuite.track_gradient_magnitude |
| 321ms             | 312ms            |  0.97 | filtering.FilteringSuite.track_median             |

Test plan

  • asv check --python=same discovers all benchmarks cleanly
  • End-to-end asv run against a local ITK v5.4.5 build (79/79 tests pass, validated 2026-04-19)
  • End-to-end run against current upstream main HEAD: CopyIterationBenchmark runs cleanly with the hardening (was SIGSEGV before this commit)
  • Probe-JSON parse keys verified against real HighPriorityRealTimeProbesCollector output
  • Consuming ITK PR's GHA workflow lands a green run

Add an airspeed-velocity (asv) harness that drives the existing native
C++ *Benchmark executables in this repository for longitudinal
performance-regression tracking. Designed to be consumed by
InsightSoftwareConsortium/ITK CI via a GitHub Actions workflow that
builds ITK at a PR's merge-base and HEAD, then invokes 'asv run'
against each build and compares the two.

Architecture

  - environment_type: 'existing' keeps ASV out of the ITK build loop.
    ASV does not recompile ITK or interpret benchmark results
    numerically; the build is managed entirely by the consuming CI
    workflow.
  - Each ASV benchmark is a track_* function returning seconds parsed
    from the jsonxx output of HighPriorityRealTimeProbesCollector,
    bypassing ASV's wall-clock instrumentation of the Python callable.
  - repo: 'itk-repo' — the caller places a symlink named 'itk-repo' at
    the harness root pointing to the ITK clone. This lets ASV validate
    --set-commit-hash values against git, so result labels track ITK
    history rather than this repository's history.

Contents

  asv.conf.json
    ASV configuration (environment_type=existing, repo=itk-repo,
    install_command installs the shim from ./python).

  python/itk_perf_shim/
    Python shim that invokes benchmark executables via subprocess,
    parses the jsonxx timings file, and returns mean probe seconds.
    registry.py maps logical benchmark names to executable name and
    CLI template. runner.py handles subprocess invocation,
    environment-variable resolution, tempfile management, and JSON
    parsing.

  benchmarks/{core,filtering,segmentation}.py
    11 track_* benchmark functions covering Core (2: copy, vector
    iteration), Filtering (5: binary_add, unary_add,
    gradient_magnitude, median, min_max_curvature_flow), and
    Segmentation (4: region_growing, watershed,
    morphological_watershed, level_set).

  README-asv.md
    Design notes, environment-variable contract, local smoke-test
    recipe, and the PR comparison pattern invoked by CI.

Scope

Registration and Resample benchmarks are intentionally omitted from
this initial harness. Their C++ sources hit ITK-level build issues
(NrrdIO airFloatQNaN link, ResampleBenchmark -Wmaybe-uninitialized)
that are unrelated to the harness and need to be addressed upstream
before those benchmarks can be included. Adding them later is a
small registry + benchmarks/ edit.

Validation

All 11 benchmarks were smoke-tested end-to-end against a local ITK
build on 2026-04-19 (ITK at v5.4.0-69-g3a8d2fa64b,
Module_PerformanceBenchmarking=ON). 'asv check' passes, 'asv run'
produces sensible timings, and 'asv compare' emits a markdown diff
table suitable for posting as a PR comment.

Supersedes the stalled 'wasm-asv' direction on thewtex's fork (last
touched 2024-05-29, 4c50054), which introduced a
pnpm+emscripten+wasi dependency surface inappropriate for a pure
regression-detection harness.
…ression

The previous CreateAndInitializeImage used
  it.Set(static_cast<PixelType>(count));
to seed pixel values. For scalar and FixedArray pixels this
performs a value cast, but for VectorImage (where PixelType is
itk::VariableLengthVector<T>) it invokes the LENGTH constructor —
producing a vector of size 'count', not a N-component pixel with
value 'count'. In particular the first iteration produces a
length-0 VLV, which ITK commit 1d87efa52 changed to store a null
data pointer.

Under ITK >= 1d87efa52, the iterator's implicit copy of
NumberOfComponentsPerPixel elements from that null source causes
a SIGSEGV. Under earlier ITK, the out-of-spec read 'happens to
work' by reading uninitialized memory. Either way, the benchmark
was never constructing semantically correct pixel values for the
VectorImage case.

Introduce a PixelFiller trait that specializes for
VariableLengthVector so the VectorImage branch constructs a
properly sized VLV and Fills it with 'count'. Scalar and
FixedArray branches keep the existing static_cast path.

With this change the benchmark produces well-defined output across
the ITK v5.3 -> main range regardless of the internal
representation of VariableLengthVector(0).
@dzenanz
Copy link
Copy Markdown
Member

dzenanz commented Apr 20, 2026

Sounds great, and looks good on a glance. I can hardly wait to see this fully integrated into our CI!

Copy link
Copy Markdown
Member

@thewtex thewtex left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 🐦

@hjmjohnson hjmjohnson marked this pull request as ready for review April 21, 2026 23:51
@hjmjohnson hjmjohnson merged commit 41bf1b9 into InsightSoftwareConsortium:master Apr 21, 2026
6 checks passed
@hjmjohnson hjmjohnson deleted the asv-native-harness branch April 21, 2026 23:52
hjmjohnson added a commit to thewtex/ITK that referenced this pull request Apr 22, 2026
Adds .github/workflows/perf-benchmark.yml — a GitHub Actions workflow
that builds ITK twice (merge-base and PR HEAD) with the
PerformanceBenchmarking remote module enabled, then drives the ASV
harness from InsightSoftwareConsortium/ITKPerformanceBenchmarking#111
against each build to produce an asv compare report.

Bumps the PerformanceBenchmarking remote module pin from
7950c1d76095033edbf7601a925cdacc2fe717f9 to
41bf1b9cfbaa1b146e1b3e5d3ad3571b2203ce1e (master HEAD, post-InsightSoftwareConsortium#111).
The new pin carries:

  * asv.conf.json, benchmarks/{core,filtering,segmentation}.py,
    python/itk_perf_shim/ (the ASV harness).
  * Hardened examples/Core/itkCopyIterationBenchmark.cxx that no
    longer crashes on VectorImage under ITK >= 1d87efa (the VLV
    null-data regression; see
    InsightSoftwareConsortium#6087 for the parallel ITK-side
    defensive null-guard).

Supersedes the 2019 Azure Pipelines macos-11 approach; macos-11 is
retired and the older evaluate-itk-performance.py driver has been
replaced by the ASV-native harness.

For the very first PR (this one) the merge-base lacks the new pin,
so its benchmark run is best-effort and the asv compare step is
wrapped with continue-on-error. The workflow becomes fully
useful for regression detection after this PR lands.
hjmjohnson added a commit to thewtex/ITK that referenced this pull request Apr 22, 2026
Adds .github/workflows/perf-benchmark.yml — a GitHub Actions workflow
that builds ITK twice (merge-base and PR HEAD) with the
PerformanceBenchmarking remote module enabled, then drives the ASV
harness from InsightSoftwareConsortium/ITKPerformanceBenchmarking#111
against each build to produce an asv compare report.

Bumps the PerformanceBenchmarking remote module pin from
7950c1d76095033edbf7601a925cdacc2fe717f9 to
41bf1b9cfbaa1b146e1b3e5d3ad3571b2203ce1e (master HEAD, post-InsightSoftwareConsortium#111).
The new pin carries:

  * asv.conf.json, benchmarks/{core,filtering,segmentation}.py,
    python/itk_perf_shim/ (the ASV harness).
  * Hardened examples/Core/itkCopyIterationBenchmark.cxx that no
    longer crashes on VectorImage under ITK >= 1d87efa (the VLV
    null-data regression; see
    InsightSoftwareConsortium#6087 for the parallel ITK-side
    defensive null-guard).

Supersedes the 2019 Azure Pipelines macos-11 approach; macos-11 is
retired and the older evaluate-itk-performance.py driver has been
replaced by the ASV-native harness.

For the very first PR (this one) the merge-base lacks the new pin,
so its benchmark run is best-effort and the asv compare step is
wrapped with continue-on-error. The workflow becomes fully
useful for regression detection after this PR lands.
hjmjohnson added a commit to thewtex/ITK that referenced this pull request Apr 23, 2026
Adds .github/workflows/perf-benchmark.yml — a GitHub Actions workflow
that builds ITK twice (merge-base and PR HEAD) with the
PerformanceBenchmarking remote module enabled, then drives the ASV
harness from InsightSoftwareConsortium/ITKPerformanceBenchmarking#111
against each build to produce an asv compare report.

Bumps the PerformanceBenchmarking remote module pin from
7950c1d76095033edbf7601a925cdacc2fe717f9 to
41bf1b9cfbaa1b146e1b3e5d3ad3571b2203ce1e (master HEAD, post-InsightSoftwareConsortium#111).
The new pin carries:

  * asv.conf.json, benchmarks/{core,filtering,segmentation}.py,
    python/itk_perf_shim/ (the ASV harness).
  * Hardened examples/Core/itkCopyIterationBenchmark.cxx that no
    longer crashes on VectorImage under ITK >= 1d87efa (the VLV
    null-data regression; see
    InsightSoftwareConsortium#6087 for the parallel ITK-side
    defensive null-guard).

Supersedes the 2019 Azure Pipelines macos-11 approach; macos-11 is
retired and the older evaluate-itk-performance.py driver has been
replaced by the ASV-native harness.

For the very first PR (this one) the merge-base lacks the new pin,
so its benchmark run is best-effort and the asv compare step is
wrapped with continue-on-error. The workflow becomes fully
useful for regression detection after this PR lands.
hjmjohnson added a commit to thewtex/ITK that referenced this pull request Apr 23, 2026
Adds .github/workflows/perf-benchmark.yml — a GitHub Actions workflow
that builds ITK twice (merge-base and PR HEAD) with the
PerformanceBenchmarking remote module enabled, then drives the ASV
harness from InsightSoftwareConsortium/ITKPerformanceBenchmarking#111
against each build to produce an asv compare report.

Bumps the PerformanceBenchmarking remote module pin from
7950c1d76095033edbf7601a925cdacc2fe717f9 to
41bf1b9cfbaa1b146e1b3e5d3ad3571b2203ce1e (master HEAD, post-InsightSoftwareConsortium#111).
The new pin carries:

  * asv.conf.json, benchmarks/{core,filtering,segmentation}.py,
    python/itk_perf_shim/ (the ASV harness).
  * Hardened examples/Core/itkCopyIterationBenchmark.cxx that no
    longer crashes on VectorImage under ITK >= 1d87efa (the VLV
    null-data regression; see
    InsightSoftwareConsortium#6087 for the parallel ITK-side
    defensive null-guard).

Supersedes the 2019 Azure Pipelines macos-11 approach; macos-11 is
retired and the older evaluate-itk-performance.py driver has been
replaced by the ASV-native harness.

For the very first PR (this one) the merge-base lacks the new pin,
so its benchmark run is best-effort and the asv compare step is
wrapped with continue-on-error. The workflow becomes fully
useful for regression detection after this PR lands.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants