The measurements
All numbers from one Raspberry Pi 4 + BerryGPS-IMU v4 (u-blox CAM-M8C), PPS on GPIO18. n = 1. Check them against your own board.
Headline progression
Section titled “Headline progression”| Change | RMS offset | Root dispersion |
|---|---|---|
| Baseline (stock kernel, stock chrony) | 823 ns | 16.8 µs |
chrony filter 10 + prefer on PPS refclock | 440 ns | 5 µs |
| PREEMPT_RT, unpatched | 2468 ns ← worse | 11.6 µs |
PREEMPT_RT + IRQF_NO_THREAD | 199 ns | 6.3 µs |
Raw PPS jitter (kernel-timestamped)
Section titled “Raw PPS jitter (kernel-timestamped)”| Kernel | jitter (σ) | peak-to-peak |
|---|---|---|
| Stock | 2134 ns | 11 µs |
| PREEMPT_RT (threaded handler) | 6947 ns | 38 µs |
| PREEMPT_RT + patch (hard-irq handler) | 2568 ns | 18 µs |
The dashboard’s tax
Section titled “The dashboard’s tax”A/B/A, 62 s per round. Why this matters.
| Dashboard off | Dashboard on | |
|---|---|---|
| Before fix | 1304 ns | 1912 / 2179 ns (+36%) |
| After fix | 1437 ns | 1169 / 1450 ns (no penalty) |
Things that did nothing
Section titled “Things that did nothing”| Change | Result |
|---|---|
| Baud 9600 → 115200 | PPS offset −1 ns either way. Identical. |
| SBAS disabled | No measurable change to PPS. |
isolcpus alone | Inconclusive-to-harmful (concentrates load onto the PPS core). |
Methodology
Section titled “Methodology”Don’t trust chrony’s own stats for this. chronyc sourcestats reports a
windowed, median-filtered figure that lags reality and hides what you’re trying to
see. Measure the kernel’s PPS timestamps directly:
sudo timeout 62 ppstest /dev/pps0 | awk '/assert/ { split($0, a, "assert "); split(a[2], b, ","); t = b[1] + 0; if (prev > 0) { d = (t - prev - 1.0) * 1e9; # deviation from exactly 1.000000000 s, in ns n++; sum += d; sumsq += d*d; if (d > max || n == 1) max = d; if (d < min || n == 1) min = d; } prev = t}END { mean = sum/n; sd = sqrt(sumsq/n - mean*mean); printf "n=%d jitter_sd=%.0f ns p2p=%.0f ns\n", n, sd, max-min}'Each pulse should be exactly 1.000000000 s after the last. The deviation is the jitter.
Always run A/B/A, never A/B. Clock behaviour drifts on the scale of minutes; if you measure on-then-off you cannot tell a real effect from thermal drift or a satellite geometry change. Go on → off → on, and require the two “on” rounds to agree before you believe the middle one.