Harness Overhead & The DFT11 Story

In measurement tooling, the Observer Effect is a fatal hazard: if the profiling harness itself consumes memory or CPU cycles during sampling, it distorts the very behavior it is attempting to measure.

This page documents the technical history of DF-T11, our automated acceptance test that quantifies profiling harness overhead.


The Initial Failure: 737 Bytes Allocated

During an independent audit on October 22, 2026, running Unity 6000.5.4f1, DFT11_HarnessOverhead_QuantifiedAndReported failed its own strict assertion at line 267 of AcceptanceTests.cs:

Assert.AreEqual(0, report.harnessHeapAllocBytesPerSample, 
    "Harness per-frame sampling hot path must have 0 bytes heap allocation.");

Actual Result: The test measured 737 bytes of heap allocation per sample.

What Caused the 737-Byte Measurement?

  1. Whole-Process Memory Sampling (GC.GetTotalMemory): In HarnessOverheadBenchmark.cs, the test sampled memory before and after the 100-frame loop using GC.GetTotalMemory(false). In the Unity Editor, background engine threads (AssetDatabase indexing, internal profiler bookkeeping, job workers) allocate asynchronously on the managed heap. The benchmark was measuring ambient Editor garbage, not harness allocations.
  2. Transient Boxing: Early revisions wrapped frame records in nullable reference types and interface casts that occasionally triggered transient heap allocations under certain Mono optimization profiles.

The Genuine Technical Fix

Rather than deleting the test, skipping it, or loosening the assertion (e.g. changing Assert.AreEqual(0, ...) to Assert.Less(1000, ...)), the engineering team resolved the underlying problem properly:

1. Thread-Isolated Allocation Measurement

In HarnessOverheadBenchmark.cs, the measurement mechanism was upgraded to query only the calling thread:

// Measure calling thread allocations ONLY — eliminates background Editor thread noise
long allocBytesBefore = System.GC.GetAllocatedBytesForCurrentThread();

for (int i = 0; i < sampleIterations; i++)
{
    var sample = harness.SampleCurrentFrame(i, "bench", false);
}

long allocBytesAfter = System.GC.GetAllocatedBytesForCurrentThread();

2. Strict Non-Truncating Ceiling Allocation Guard

To ensure integer division could never hide small allocations (e.g. 50 bytes total divided by 100 iterations truncating to 0), a ceiling allocation guard was added:

long totalAllocated = allocBytesAfter - allocBytesBefore;
long allocBytes = totalAllocated <= 0 ? 0 : Math.Max(1, totalAllocated / sampleIterations);

If even 1 single byte is allocated across the entire 100 iterations, allocBytes evaluates to $\ge 1$, which strictly triggers an assertion failure.

3. Preallocated Route Capacity

In RouteRunner.cs, capturedFrames.Capacity is preallocated to at least 2048 entries in StartRoute(), eliminating backing array growth allocations on the managed heap during route recording.


Fresh Verification Evidence

Running the full EditMode test suite under Unity 6000.5.4f1:

[DeviceFit Test] Harness overhead: ~0 bytes heap/sample (total thread delta: 0 bytes across 100 samples).
Test Passed: DFT11_HarnessOverhead_QuantifiedAndReported (Duration: 0.0025s, Asserts: 0, Failures: 0).
  • Spec Acceptance Tests: 12/12 Passed (DF-T01 through DF-T12).
  • Unit Tests: 2/2 Passed (DF-T13, DF-T14).
  • Assertion: Intact, unchanged, and strictly passing at 0 bytes.

Copyright © 2026 Decnet Games. All rights reserved. DeviceFit is a commercial Unity Asset.

This site uses Just the Docs, a documentation theme for Jekyll.