Charlie NapierPurdue CompE, class of 2029

Configuring an interrupt handler barely changes how fast it usually responds. It changes the worst case sevenfold.

7.6×worse at the 99th percentile
best case
326clocks
240 MHz core0 iram L2 driver idle
worst case
2,489clocks
240 MHz core0 iram L1 driver isr

Same chip, same 240 MHz clock. Their medians differ by 6 clocks, so on the median they look identical. The worst case is easy to overlook, and it is the number that matters once timing has to hold.

median99th percentile
Median versus 99th-percentile interrupt latency for four background loads at 240 MHz. All four medians sit at about 330 CPU clocks; the 99th percentiles range from 332 to 2479 clocks. 0 500 1,000 1,500 2,000 2,500 idle 332 CPU busy 332 cache thrash 1,735 competing ISR 2,479 clocks
  1. idle332
  2. CPU busy332
  3. cache thrash1,735
  4. competing ISR2,479

bars run from median to p99, on a scale that starts at zero and ends at 2,600 clocks

240 MHz, linear scale from zero. Loading the CPU with arithmetic changes nothing. Competing for the instruction cache or the interrupt controller multiplies the tail by 5 to 7. Each bar is the median across the 24 configurations that share that background load, which is why the competing-ISR bar reads 2,479 rather than the 2,489 of the single worst configuration above.

How it was measured

A pin drives itself and times its own interrupt. 576,000 interrupts across 288 configurations, with no wiring and no scope.

trigger
GPIO4 in INPUT_OUTPUT mode drives the pin and watches it at once, so writing it high raises a real interrupt through the full hardware path. No wire is connected.
clock
The Xtensa CCOUNT cycle counter, read immediately before the triggering store and as the handler's first statement. Reading it costs 1 clock, measured and published rather than subtracted.
sampling
2,000 interrupts per configuration, with the gap between them randomised. A fixed gap phase-locks with the 1000 Hz scheduler tick and yields a tight, entirely fictional distribution.
sweep
Six factors crossed into 288 configurations. Percentiles are computed on-chip across every sample.
automation
CPU frequency needs a rebuild, so a Python driver builds, flashes and captures once per clock speed (240 / 160 / 80 MHz) and merges the runs.

Explore every configuration

All 288 configurations. Preloaded with the best and worst from above plus a cache-thrashing one — change any control to re-plot.

Which factor actually matters

Each bar runs from a factor's best setting to its worst across all 288 configurations, with a dot for every setting in between, ranked by how far apart the two ends are. Switch the statistic and the ranking changes completely: on the median none of it matters, in the tail one factor dominates everything else.

measured at the

Pick configurations to compare

The filters narrow the list; clicking a row plots it below. Up to three at a time.

288 configurations match.

Distribution of the selected configurations

view

Latency against percentile. A flat line is a predictable interrupt; a curve that lifts at the right is one that is usually fast and occasionally is not.

Data table

The selected configurations in full, in CPU clocks — the text alternative to the charts above. A value shown as ≥4,095 hit the tally ceiling; read max for those rows.

configurationnminmeanp50p90p99p99.9max
240 MHz core0 iram L2 driver idle2,000322325.1325326326360465
240 MHz core0 flash L2 raw cache2,0003281018.83302,1912,1912,1912,191
240 MHz core0 iram L1 driver isr2,000327515.53313442,4892,5163,863

Where this measurement is incomplete

Why I built this

I got curious about how accurate and reliable interrupt timing actually is under different conditions, and started measuring it instead of assuming it. The results mattered more than I expected: the median barely moves no matter what changes, so it is easy to check the typical case, see nothing wrong, and miss a real problem sitting in the tail.