Skip to content

Performance Baseline Tests

These tests serve as automated CI regression guards. They verify that critical operations complete within acceptable time bounds, detecting performance regressions before they reach production.

Baseline Philosophy

Baselines are set generously (2-3x expected typical performance) to account for CI environment variability while still catching significant regressions. A test failure indicates a performance regression that needs investigation.

Test Categories

  • Spatial Trees: QuadTree2D, KdTree2D, KdTree3D, OctTree3D, RTree2D construction and query performance
  • PRNG: Random number generation throughput for PcgRandom, XoroShiroRandom, SplitMix64, RomuDuo
  • Pooling: Collection pool rent/return overhead for List, HashSet, Dictionary, StringBuilder, SystemArrayPool
  • Serialization: JSON and Protobuf serialization/deserialization throughput

Performance Baseline Report

Generated: 2026-01-12 01:36:55 UTC

Spatial Trees

Test Iterations Time (ms) Baseline (ms) % of Baseline Status
QuadTree2DRangeQuery1K2720013.5%Pass
QuadTree2DBoundsQuery1K2920014.5%Pass
KdTree2DRangeQuery1K2720013.5%Pass
KdTree2DNearestNeighbor1K3220016.0%Pass
RTree2DRangeQuery1K24792001239.5%FAIL
OctTree3DRangeQuery1K152007.5%Pass
KdTree3DRangeQuery1K3320016.5%Pass
QuadTree2DConstruction125000.4%Pass
KdTree2DConstruction125000.4%Pass
RTree2DConstruction115000.2%Pass

PRNG

Test Iterations Time (ms) Baseline (ms) % of Baseline Status
PcgRandomNextInt1M15000.2%Pass
PcgRandomNextFloat1M55001.0%Pass
XoroShiroRandomNextInt1M15000.2%Pass
SplitMix64NextInt1M15000.2%Pass
RomuDuoNextInt1M15000.2%Pass

Pooling

Test Iterations Time (ms) Baseline (ms) % of Baseline Status
ListPooling100K239504200119752.0%FAIL
HashSetPooling100K165032008251.5%FAIL
DictionaryPooling100K169972008498.5%FAIL
SystemArrayPool100K82004.0%Pass
StringBuilderPooling100K164562008228.0%FAIL

Serialization

Test Iterations Time (ms) Baseline (ms) % of Baseline Status
JsonSerialize10K435008.6%Pass
JsonDeserialize10K6450012.8%Pass
JsonRoundTrip10K113100011.3%Pass
ProtobufSerialize10K1169500233.8%FAIL
ProtobufDeserialize10K125002.4%Pass
ProtobufRoundTrip10K17281000172.8%FAIL

Summary

19 passed, 7 failed out of 26 tests.

Running the Tests

These tests run automatically during CI to catch regressions. To generate fresh benchmark results:

  1. Open Unity Test Runner
  2. Navigate to PerformanceBaselineTests
  3. Run GeneratePerformanceBaselineReport explicitly (it is marked [Explicit])
  4. Results will be output to the console and can be copied to this document

Interpreting Results

  • Time (ms): Actual measured time for the operation
  • Baseline (ms): Maximum allowed time before test failure
  • % of Baseline: How much of the baseline budget was used (lower is better)
  • Status: Pass if within baseline, Fail if exceeded

Refreshing these numbers

Run PerformanceBaselineTests.GeneratePerformanceBaselineReport from Unity's Test Runner.

Calibrated paired evidence

The generous baseline budgets above detect large regressions. They do not establish that an optimization meets the acceptance criteria in issue #636. Scheduled aggregate reports are advisory. Their renderer never writes the canonical baseline, including after a regression, and refuses empty, malformed, duplicate, or incomplete metric sets. A positive cost against a zero baseline is a regression. The previous automatic baseline update option is rejected.

BenchmarkProtocol.MeasureCalibrated now provides the first part of the stronger protocol:

  • Correctness runs before warmup and again after the retained timing samples.
  • Each arm warms separately for at least 100 ms and three executions. Calibration doubles the common iteration count until both arms take at least 20 ms; inability to calibrate is inconclusive.
  • Eight predeclared ABBABAAB batches retain 32 observations per arm. Each slot begins with heap settling, consumes a checksum, and must take at least 10 ms. There are no acceptance retries.
  • Immutable raw milliseconds, paired log ratios, geometric throughput ratio, median, p95, MAD, and within-arm spread accompany a 95% interval from 2,000 deterministic bootstrap repetitions. Bootstrap resamples whole counterbalanced batches to preserve within-batch correlation.
  • Environment metadata records commit/base commit, Unity version, backend, build configuration, OS, CPU, process width, platform, and whether execution is inside the Editor. Unavailable code generation and optimization settings remain unknown. Seed, iteration count, and measured warmup durations/executions accompany the samples.

IntMapPerformanceTests emits the full raw record as INTMAP_PAIRED_SAMPLES JSON in its NUnit output through an explicit JSON writer that needs no runtime serializer generation. Incomplete counterbalanced batches cannot claim timing acceptance and report an undefined interval as JSON null. Existing fixtures that still call MeasurePaired retain their older advisory protocol; this change does not make their tables calibrated evidence.

For the IntMap player experiment, manually dispatch Unity Tests with acceptance=intmap and a supported unity-version. Normal selected tests remain mandatory. The extra test runs in a fresh Release IL2CPP player against Dictionary compiled in the same candidate, so its commit and reference commit metadata are identical. The artifact retains all four workload records. The verifier derives ratios, spreads and the bootstrap interval from the raw samples. It reports inconclusive for unstable arms, meets-hit-margin for stable results with at least 1.3× at both hit-only sizes and a favorable interval, or below-hit-margin. This is the timing decision for #578; it is not full #636 acceptance or proof of unmeasured allocation and code-size properties. No IntMap player result has been claimed before this workflow actually runs.

The encoded timing improvement predicate requires at least 5% less runtime (throughput ratio at least 1 / 0.95), an entirely favorable interval, and stable arms. The allocation-change timing predicate requires the entire runtime interval to stay within 5% of the reference. These are timing predicates only: they cannot accept a change without green instrument controls, identical semantics, and verified allocation, retention, and code-size evidence. Unsupported counters are explicitly labeled; an absent measurement never becomes zero.

Issue #636 remains open. Required follow-up includes calibrated allocating/non-allocating and fast/slow player canaries, workload-specific retention probes and build-size measurements, the full acceptance policy including declared tradeoffs, and explicit post-merge promotion after 20 clean floor/latest Mono/IL2CPP player calibration repetitions. There is currently no automatic promotion command. Analyzer findings, changed-branch coverage, mutation, replay/minimization, and touched-group CI tiers also remain separate requirements. No new measured performance numbers or nonempty baseline are claimed by this infrastructure change.