Configuring an interrupt handler barely changes how fast it usually responds. It changes the worst case sevenfold.
- 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.
- idle332
- CPU busy332
- cache thrash1,735
- competing ISR2,479
bars run from median to p99, on a scale that starts at zero and ends at 2,600 clocks
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.
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.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.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.
Pick configurations to compare
The filters narrow the list; clicking a row plots it below. Up to three at a time.
Distribution of the selected configurations
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.
Where a curve lifts, the histogram says whether it lifts smoothly or jumps. The cache-thrash row is the one worth looking at: it is not a slow distribution but two separate ones, 62% of samples near 328 clocks and 37% near 2,184, with almost nothing in between. Counts are on a logarithmic scale, because the rare tail is the point and a linear axis flattens it into the baseline.
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.
| configuration | n | min | mean | p50 | p90 | p99 | p99.9 | max |
|---|---|---|---|---|---|---|---|---|
| 240 MHz core0 iram L2 driver idle | 2,000 | 322 | 325.1 | 325 | 326 | 326 | 360 | 465 |
| 240 MHz core0 flash L2 raw cache | 2,000 | 328 | 1018.8 | 330 | 2,191 | 2,191 | 2,191 | 2,191 |
| 240 MHz core0 iram L1 driver isr | 2,000 | 327 | 515.5 | 331 | 344 | 2,489 | 2,516 | 3,863 |
Where this measurement is incomplete
- 10 of 288 rows have a censored 99.9th percentile. The on-chip tally array stops at 4,095 clocks, so anything beyond lands in the top bin. Those rows read ≥4,095, and the unclamped
maxcolumn carries the real figure. The 50th, 90th and 99th percentiles are unaffected everywhere. - Nothing here has the radio on. Wi-Fi is the largest source of interrupt jitter on a real ESP32-S3 and it is not in this sweep: its handlers compete for the same interrupt controller these configurations use, and how long they hold it is not something the application controls. Treat every number on this page as a floor for a system with a radio, not an estimate of one.
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.