Menu

Searches titles, sections and text. ↑ ↓ to move, Enter to open, Esc to close. Opens with / or Ctrl+K.

    Contact

    What instrumentation costs, and when it is still the right answer

    The overhead argument is weaker than it used to be, and the representativeness argument is not. An honest account of where each applies.


    Coverage by instrumentation is the default for good reasons. It is well-understood, it works on any target, the tooling is mature, and for a large class of projects it is exactly right.

    It is also the thing trace-based measurement is usually positioned against, often with an argument that no longer holds as strongly as it once did. Here is the honest version.

    In brief

    Coverage instrumentation adds counters to the program. Its costs are of three different kinds: runtime perturbation, code and RAM footprint, and the structural fact that the measured artifact differs from the shipped one. The first two are workload-dependent and sometimes small; the third does not get weaker. A measured, real-scale SQLite -O3 comparison below shows all three at once.

    What instrumentation does

    Instrumentation schemes insert observation code and state according to their own model. For example, GCC -fprofile-arcs instruments selected CFG arcs outside a spanning tree and reconstructs the remaining arc counts; condition coverage records exercised condition effects in compiler-generated state. Other tools may instrument statements, blocks, branches, paths or conditions differently. Resource and semantic claims must therefore identify the exact tool, version and options. The program updates that state as it runs; afterwards it is read out and turned into a report.

    Three costs follow, and they are of different kinds.

    EffectQuestionTypical evidence
    Runtime perturbationDid execution time or scheduling change?repeated target measurements and uncertainty
    Code/data footprintDid sections, placement or cache behavior change?map files, sizes and layout comparison
    Optimization effectDid generated control flow or register allocation change?object-code and compiler-output comparison
    RepresentativenessWhat property of the delivered executable can the instrumented result support?configuration-control and transfer argument

    Cost 1: runtime, and why this argument got weaker

    The intuitive objection is that counters cost time. They do - and not always by much. In cases we have observed, generated counter updates and layout changes produced a net runtime effect within the observed variation, and one instrumented build measured marginally faster than the uninstrumented one: at that magnitude, layout effects dominate the counters. The mechanism behind such a result belongs in the object code and the measurement record, not in a generic claim about register retention.

    So the blunt form of the argument - “instrumentation is slow” - is not reliably true, and stating it as a general claim invites a fair rebuttal. Some optimized counter implementations have little measurable runtime effect in particular loops or workloads. That finding should be reported, not hidden. It does not establish zero effect for the complete application, and it does not remove code-size, layout or artifact-identity questions.

    The precise form survives: the cost is workload- and layout-dependent, up to changing sign. That is exactly what limits its value as timing evidence for the production binary. An instrumented timing result is not a direct measurement of the shipped binary. Any transfer requires an explicit probe-effect/equivalence analysis with stated bounds; where that cannot be established, report the production timing as unknown from this run.

    Cost 2: memory

    Counters live somewhere. On a host, irrelevant. On a target with a fixed RAM budget and a linker script that is already tight, the instrumented build may simply not fit - and the amount is predictable enough to plan for, which is why this cost is the least controversial of the three.

    Cost 3: the artifact is a different artifact

    This is the one that does not get weaker.

    The build you measured has different code, different layout, different timing and different memory behavior from the build you ship. Everything you conclude from it transfers by argument, not by measurement.

    For unit-level testing that argument is easy and nobody objects. At integration and system level, on real hardware, with timing-dependent behavior, it is the argument an assessor pushes on.

    All three costs can be seen together in one measured, real-scale example: SQLite at -O3, measured on a Cortex®-M7 target directly after the two-run argument below.

    The two-run counter-argument

    There is a well-established practice that addresses this: run the tests twice, once instrumented and once not, compare the outputs, and if they agree, argue that the coverage measured in the first run applies to the second.

    It is a reasonable method and it is used in certified projects. It is worth being precise about what it establishes: output equivalence, not coverage transfer. Structure was observed only in the instrumented run. The transfer step is a representativeness argument - a good one, often accepted, and still an argument rather than a measurement.

    It also has four places where it does not reach:

    • Residual object code that exists only in the shipped binary, and is absent or relocated in the instrumented build.
    • MC/DC under optimization, where the two builds do not have the same condition structure to begin with.
    • Timing-dependent interleavings - interrupts, DMA - where two runs are not the same execution regardless of output equality.
    • Integration level with binary-only components, where the instrumented build cannot be produced at all.

    A measured comparison: SQLite at -O3

    One benchmark cannot predict the overhead of another program. It can, however, show where each observation method pays and which evidence remains available after optimization.

    The example below uses a real-scale SQLite 3.45.3-based workload compiled at -O3 for an Arm® Cortex®-M7 target. The complete record - setup, evidence status, source-attribution results and machine-readable dataset downloads - is the SQLite -O3 Evidence Record. It compares the original target binary, CEDARtools.Anchor and GCC coverage across four separate dimensions:

    • code size;
    • static RAM;
    • observed runtime;
    • source attribution.

    These dimensions must be read separately. A method may have little runtime effect while substantially increasing code or RAM. A complete machine-level trace may still be insufficient to identify every source-level coverage obligation.

    Three ways to observe the same workload

    Native trace and DWARF

    The native variant is the original optimized target binary, built without coverage counters or anchors. Hardware instruction trace records the executed machine-level control flow. Debug information is then used to attribute that execution to source-level structures.

    This path leaves the application instruction stream unchanged. Its limitation is not necessarily trace completeness: compiler optimization may merge, predicate, clone or remove source-level structures so that an observed machine branch no longer identifies one source obligation unambiguously.

    CEDARtools.Anchor

    The Anchor* variants supplement selected source-level paths and conditions with named control-flow anchors. The anchors preserve source identity in the generated object code and symbol information. Their execution is observed through hardware trace and evaluated outside the target.

    The anchors occupy code space and can affect code layout. They do not maintain coverage counters or analysis state in application RAM.

    GCC coverage

    The GCC variants use compiler-generated software instrumentation. The statement and decision comparison uses GCC coverage counters; the L4 comparison additionally uses GCC condition coverage.

    The compiler inserts counter-update code and allocates coverage state in RAM. In this SQLite configuration, the projected complete instrumented images exceed the available target memory, so no on-target GCC runtime value exists.

    The term “L4 comparison” describes the condition-level configuration used in this experiment. It must not be presented as evidence that GCC condition coverage is semantically identical to every MC/DC interpretation used by certification-oriented tools or standards.

    What source attribution means

    Source attribution is the ability to assign observed machine-level execution to the correct source-level coverage obligation without ambiguity.

    It is separate from both trace completeness and coverage achieved.

    A trace can be complete at the machine level while optimization has merged, predicated or removed the source structure required to determine which source condition produced the observed event. In that case, execution was observed, but the observation cannot safely be attributed to one source-level obligation.

    For the native binary, source attribution depends on the source-to-object relationship retained in debug information and in the optimized control flow.

    Anchor* preserves the identity of selected paths and conditions through named anchors. Missing or colliding anchors can therefore be reported as detected attribution shortfalls rather than silently treated as covered.

    GCC instrumentation preserves source identity by placing its own counters at compiler-selected observation points. That identity comes with the code, RAM and runtime effects reported separately below. It is also line-level identity: for condition-level MC/DC, GCC’s condition coverage derives condition outcomes from the optimized control-flow graph, so a condition the optimizer merges is gone from GCC’s own report as well, whereas Clang’s source-based coverage keeps the source conditions through optimization because it instruments before the optimizer runs - measured with both compilers.

    Trace completeness asks: did we observe the machine execution?

    Source attribution asks: can we assign that observation to the correct source obligation?

    Coverage asks: which attributable obligations were exercised?

    Measurement setup

    • Workload: SQLite 3.45.3-based real-scale corpus; target configuration using the report’s LEMON-rebuilt subset
    • Optimization: -O3
    • Target: STM32H7S3L8 on NUCLEO-H7S3L8
    • Processor: Arm Cortex-M7 at 600 MHz
    • Target load regions: 456 KB AXISRAM, 64 KB ITCM and 64 KB DTCM
    • Aggregate load-region capacity: 584 KB
    • GCC toolchain used for the measured deltas: arm-none-eabi-gcc 14.2.1
    • Measurement snapshot: 13 July 2026
    • Test level shown: system test

    Static size, runtime and source attribution have different evidence paths and are labeled accordingly. Native and Anchor* code and RAM values are static artifact measurements. Runtime values come from a documented on-target rate-mode campaign. The GCC code and RAM values are projections from measured translation-unit deltas because the complete GCC images do not fit this target configuration. Source attribution is derived from source-to-object analysis rather than from elapsed-time measurement.

    The complete comparison - both levels, every value with its evidence status - follows in the data tables at the end of this page.

    When each is right

    An honest decision table, without a product in it:

    SituationVerification questionReasonable methods
    Host unit tests, functionalstatement / branch executioninstrumentation; nothing else is warranted
    Target unit tests, rebuildablestatement / branch executioninstrumentation is fine; trace adds representativeness you may not need
    Integration, optimized and timing-sensitiveobject instruction / object branch executionpassive trace where the configured protocol and continuity evidence support the metric
    Integration, optimized and timing-sensitivesource condition / MC/DCpassive trace only where each required condition outcome is recoverable; otherwise validated supplementation, instrumentation on a justified representative build, formal/static evidence, or another project-approved method
    Highest criticality, generated object-code structurescorrectness, unintended function and applicable object-code structural evidenceobject-code analysis plus requirements-based verification; trace can establish observed execution but does not by itself complete the disposition

    The first two rows are the majority of testing that happens in the world. It would be dishonest to pretend otherwise.

    And a caveat about our own side

    CEDARtools.Anchor should be evaluated by the same discipline. It avoids application-side coverage-state writes, but the observation anchors add instructions and may change layout or timing. We do not criticize software instrumentation for perturbation while describing Anchor* as effect-free.

    “Non-intrusive” is a gradient, not a switch. A trace-based approach does not touch the code, but the trace unit has to be configured, the trace port has to be routed, and enabling trace is not literally free at the silicon level. Approaches that add anything to the build - even something that writes nothing to memory - are supplementing it, and the honest word for that is supplementation, not zero cost.

    We would rather write that down than let the word “non-intrusive” do work it cannot support.

    Choose the observation method according to the verification question. Instrumentation is often appropriate for detailed unit-level feedback. Passive processor trace is attractive when application-side counters or a separate executable would weaken an integration or timing argument. Anchor* is an explicit trade-off where source-condition observability is required and passive reconstruction is insufficient.

    In this context, the following expressions refer to closely related aspects of the same question: instrumentation cost; coverage instrumentation overhead; software instrumentation overhead; probe effect of coverage instrumentation. This page uses instrumentation cost as the canonical term.

    What this page establishes

    • the three cost classes of instrumentation and the evidence each needs;
    • what the two-run practice establishes (output equivalence) and where it does not reach;
    • a decision table for when instrumentation is the right answer;
    • a measured comparison with its Evidence Record.

    Limitations

    Every number on this page is specific to its workload, compiler, optimization level and target configuration. No percentage from one program predicts another.

    Sources

    The instrumentation mechanism described above is documented by its own project:

    Our own measured figures are labeled as such, with the build and date.

    Where this becomes a product decision

    Apply this to your target

    Send the exact processor, board, trace path, compiler and metric you need. We will review feasibility at configuration level rather than answering from the core-family name alone.

    Start a target feasibility review


    Related: the mapping problem

    Statement and decision class - SQLite 3.45.3-based target workload, -O3,Arm Cortex-M7 (STM32H7S3L8 / NUCLEO-H7S3L8); snapshot 2026-07-13.
    DimensionNative trace + DWARFCEDARtools.AnchorGCC coverage
    Code (.text)413,512 BMEASURED · STATIC448,352 B108.4% of native+8.4%MEASURED · STATIC777,399 B188.0% of native+88.0%PROJECTEDProjected from measured TU deltas
    Static RAM (.data + .bss)54,164 BMEASURED · STATIC54,156 Beffectively unchanged (100.0% of native)MEASURED · STATIC233,760 B431.6% of native+331.6%PROJECTEDProjected from measured TU deltas
    Observed runtime41.2 ms observedMEASURED · ON TARGET39.4 ms observedL0 variant, 25.39 iter/s; 95.6% of native (rate-derived). The L1 variant measured 36.9 msObserved layout effect; not a speedup claimMEASURED · ON TARGETNot runnable on this targetNOT RUNNABLE
    Source attribution77%of the decision obligations linked into the image attributable without ambiguityDERIVED>99.9%7,607 of 7,611 expected anchors resolvedDERIVEDSelf-identifyingobservation points carry their own identity within the GCC instrumentation modelDERIVED
    Target fitFits; ran on targetFits; ran on target✕ does not fit this target configurationprojected complete image ≈ 1,041 KB
    Evidence statusstatic sizes measured; runtime from documented on-target campaignstatic sizes measured; runtime from documented on-target campaigncode and RAM projected from measured translation-unit deltas; no runtime value exists
    Condition / MC/DC-class comparison - same workload, target and snapshot. "L4" names the condition-level configuration of this experiment, not a claim of semantic identity with every MC/DC interpretation.
    DimensionNative trace + DWARFCEDARtools.AnchorGCC coverage
    Code (.text)413,512 BMEASURED · STATIC461,160 B111.5% of native+11.5%MEASURED · STATIC986,938 B238.7% of native+138.7%PROJECTEDProjected from measured TU deltas
    Static RAM (.data + .bss)54,164 BMEASURED · STATIC54,156 Beffectively unchanged (100.0% of native)MEASURED · STATIC364,568 B673.1% of native+573.1%PROJECTEDProjected from measured TU deltas
    Observed runtime41.2 ms observedMEASURED · ON TARGET37.4 ms observed26.75/26.74 iter/s, 2 × 60 s; 90.7% of native (rate-derived)Observed layout effect; not a speedup claimMEASURED · ON TARGETNot runnable on this targetNOT RUNNABLE
    Source attribution48.2% of condition obligations1,470 of 3,047 resolved; 51.6% of compound decisions complete (646 of 1,251)DERIVEDAvailable for the anchored obligation setselected obligations preserved through named anchors; denominators independently reproduced 2026-08-16DERIVEDSelf-identifyingobservation points carry their own identity within the GCC instrumentation modelDERIVED
    Target fitFits; ran on targetFits; ran on target✕ does not fit this target configurationprojected complete image ≈ 1,373 KB
    Evidence statusstatic sizes measured; runtime from documented on-target campaignstatic sizes measured; runtime from documented on-target campaigncode and RAM projected from measured translation-unit deltas; no runtime value exists

    These attribution labels do not establish that every source-level obligation is represented, nor that GCC condition coverage is semantically identical to every certification-oriented MC/DC interpretation.

    Reading the result

    For the statement and decision class, CEDARtools.Anchor increased .textfrom 413,512 B to 448,352 B, an increase of 8.4%. Static RAM remained effectively unchanged.

    The GCC coverage image is projected at 777,399 B of code and 233,760 B of static RAM. The projected complete image is approximately 1,041 KB and does not fit this target configuration. Because there is no launchable target image, no GCC runtime value is reported.

    For the L4 condition/MC/DC-class comparison, CEDARtools.Anchor increased.text to 461,160 B, 11.5% above native, again without a material static-RAM increase.

    The corresponding GCC image is projected at 986,938 B of code and 364,568 B of static RAM. Its projected complete image is approximately 1,373 KB and does not fit the target configuration.

    The observed CEDARtools.Anchor runtimes were 39.4 ms (L0), 36.9 ms (L1) and 37.4 ms (L4) per iteration, compared with 41.2 ms for the native runtime campaign - rate-derived from the iteration-rate ledger (campaign of 3 July 2026). These observations are not evidence that anchors make the workload faster. The non-monotonic result across supplement levels is consistent with code-layout and cache-alignment effects dominating a small intrinsic anchor cost in this particular workload.

    The native trace supported unambiguous source attribution for 77% (5,667 of 7,356) of the decision obligations linked into the image. At L4, attribution was available for 48.2% (1,470 of 3,047) of the conditions in compound decisions, while 51.6% (646 of 1,251) of the compound decisions were completely derivable.

    This does not mean that the remaining execution was absent from the trace. It means that the optimized machine-level event could not always be assigned to one source-level obligation without ambiguity.

    CEDARtools.Anchor and GCC preserve source identity by different mechanisms: named control-flow anchors for CEDARtools.Anchor, compiler-generated counters for GCC. Their resource effects must therefore be compared separately from the attribution result.

    What this example establishes

    • CEDARtools.Anchor shifts its primary target-side cost into code rather than analysis RAM.
    • GCC instrumentation uses both code and RAM in this configuration.
    • The complete GCC images do not fit this target, so a missing runtime value is a result rather than an omitted measurement.
    • Trace completeness and source attribution are different evidence properties.
    • Compiler optimization can reduce source attribution even when machine execution has been observed.
    • These figures describe this workload, toolchain and target configuration. They are not a universal overhead claim.
    Measurement details and provenance

    The GCC projections are based on measured deltas from the same translation unit and compiler flags. They are not measurements of a successfully linked and executed full-target GCC coverage image.

    The static-size comparison and the rate-mode runtime campaign are separate evidence paths. The website does not imply that all values were extracted from one identical binary artifact.

    Measurement snapshot 2026-07-13; GCC toolchain for the measured deltas: arm-none-eabi-gcc 14.2.1. Target load regions:456 KB AXISRAM, 64 KB ITCM,64 KB DTCM (584 KB aggregate).