Advance navigation bit clocks across missing measurements - #235
Conversation
…all one The bit-completion test was `count == blocks_per_bit`. A record whose length does not divide the symbol steps the count *past* the boundary, and from then on equality never holds again: that satellite stops producing navigation bits for the rest of its lock while still reporting bit sync as found. Measured on a hardware-correlator receiver (GNSSReceiver.jl#107) folding one 2 ms record per processing chunk: 90 s of unbroken lock, bit sync found at t = 25 s, one soft bit. The same hazard reaches the sample-driven path through `set_preferred_num_code_blocks_to_integrate!` and any producer whose record stream has holes. `_complete_bits!` drains while `>=` and carries the remainder, so a record longer than a whole symbol yields every bit it spans. Identical for every history that did not already stall. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A producer that knows signal time elapsed but has nothing to report had no way to say so. `append_correlator_output!` means "here is a measurement", so the only way to keep a satellite's navigation bit boundary across a hole in a hardware correlator's record stream was to append an all-zero `CorrelatorOutput` spanning it and hope the consumer saw through the fake. It does not: every discriminator computes 0/0 on a zero correlator, and the NaN leaves the loop filter as a NaN Doppler. That is the actual source of the zero-energy correlators the discriminator guards were added for — nothing in a device produces one. The guards stay as defence for third-party producers, but they were treating a symptom, and a quiet one: with them a gap no longer crashes yet still feeds a zero-*error* measurement to the loop filter, a zero prompt to the prompt filter and a zero-power record to the C/N₀ average. `advance_bit_clock!` sits beside `append_correlator_output!`, is addressed the same way, and moves nothing but the bit buffer. Pre-sync it shifts the missing blocks through the hard-decision search window and advances its length, so both the sliding-window search and the CFAR phase index stay on the true block grid; post-sync it credits them through the same drain `buffer` uses, so a gap longer than a symbol yields every bit it spans. A secondary-coded signal's overlay phase rolls by the gap rather than going stale. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #235 +/- ##
==========================================
- Coverage 98.32% 98.18% -0.14%
==========================================
Files 47 47
Lines 3990 4016 +26
==========================================
+ Hits 3923 3943 +20
- Misses 67 73 +6 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Benchmark Results (minimum time) — ubuntu-latestReporting the minimum over all samples (robust to shared-runner contention), not the median. Alternative backends vs Float32 (
|
| Scenario | F32 | I16 | 1b | 2b | ×I16 | ×1b | ×2b |
|---|---|---|---|---|---|---|---|
| GPS L1CA, 1 sat, 3 signals @ 5 MHz | 10.7 μs | 7.41 μs | 6.7 μs | 9.13 μs | 1.44 ✅ | 1.6 ✅ | 1.17 ✅ |
| GPS L1CA, 1 sat, 4 antennas @ 5 MHz | 13.2 μs | 7.28 μs | 6.56 μs | 11.9 μs | 1.81 ✅ | 2.01 ✅ | 1.11 ✅ |
| GPS L1CA, 8 sats @ 40 MHz | 200.0 μs | 80.7 μs | 47.8 μs | 77.0 μs | 2.47 ✅ | 4.17 ✅ | 2.59 ✅ |
| GPS L1CA, 8 sats @ 5 MHz, 100 ms buffer | 3.06 ms | 1.7 ms | 1.24 ms | 1.68 ms | 1.8 ✅ | 2.46 ✅ | 1.82 ✅ |
| GPS L1CA, 8 sats @ 5 MHz | 33.6 μs | 20.4 μs | 15.9 μs | 21.4 μs | 1.65 ✅ | 2.11 ✅ | 1.57 ✅ |
| Galileo E1B, 4 sats @ 25 MHz | 90.0 μs | 42.7 μs | 2.11 ✅ | ||||
| dynamic taps @ 5 MHz (kernel) | 5.79 μs | 3.38 μs | 2.05 μs | 2.79 μs | 1.71 ✅ | 2.83 ✅ | 2.07 ✅ |
Time benchmarks (base vs PR head)
Ratio = 7a55f91… / d71924b…: >1 means the PR is faster. ✅ ≥ 5 % faster,
| 7a55f91… | d71924b… | 7a55f91… / d71924b… | |
|---|---|---|---|
| downconvert and correlate/CPU/Float32 | 2.4 μs | 2.4 μs | 1.0 |
| downconvert and correlate/CPU/Float32 4ant | 4.78 μs | 4.79 μs | 0.997 |
| downconvert and correlate/CPU/Float64 | 2.8 μs | 2.8 μs | 1.0 |
| downconvert and correlate/CPU/Int16 | 2.48 μs | 2.48 μs | 1.0 |
| downconvert and correlate/CPU/Int16 4ant | 5.23 μs | 5.23 μs | 1.0 |
| downconvert and correlate/CPU/Int32 | 2.45 μs | 2.55 μs | 0.961 |
| fused kernel/1-ant dynamic taps | 2.16 μs | 2.16 μs | 1.0 |
| fused kernel/1-ant static taps | 1.88 μs | 1.88 μs | 1.0 |
| fused kernel/4-ant dynamic taps | 5.34 μs | 5.33 μs | 1.0 |
| fused kernel/4-ant static taps | 4.14 μs | 4.16 μs | 0.996 |
| fused tuple kernel/1-ant N=2 | 2.35 μs | 2.37 μs | 0.992 |
| fused tuple kernel/1-ant N=3 | 2.81 μs | 2.82 μs | 1.0 |
| fused tuple kernel/2-ant N=2 | 3.62 μs | 3.6 μs | 1.01 |
| fused tuple kernel/2-ant N=3 | 4.54 μs | 4.56 μs | 0.995 |
| fused tuple kernel/4-ant N=2 | 6.07 μs | 6.03 μs | 1.01 |
| fused tuple kernel/4-ant N=3 | 9.77 μs | 9.75 μs | 1.0 |
| noise estimation/append_noise_observation! – steady state | 30.8 ns | 32.9 ns | 0.934 |
| noise estimation/track! – GPS L1CA, 1 sat, 2K @ 5 MHz with a noise reference | 5.2 μs | 5.2 μs | 0.999 |
| noise estimation/update_noise! – 1 ms @ 20 MHz | 19.3 μs | 19.2 μs | 1.0 |
| noise estimation/update_noise! – 1 ms @ 4 MHz | 4.34 μs | 4.42 μs | 0.982 |
| noise estimation/update_noise! – 1 ms @ 4 MHz, 4 antennas | 8.86 μs | 8.85 μs | 1.0 |
| track/GPS L1CA + Galileo E1B, 8+8 sats, 25K @ 25 MHz – in-place, Float32 | 487.0 μs | 487.0 μs | 0.999 |
| track/GPS L1CA + Galileo E1B, 8+8 sats, 25K @ 25 MHz – in-place, Float32, threaded | 233.0 μs | 247.0 μs | 0.942 |
| track/GPS L1CA + Galileo E1B, 8+8 sats, 25K @ 25 MHz – out-of-place, Float32 | 491.0 μs | 490.0 μs | 1.0 |
| track/GPS L1CA + Galileo E1B, 8+8 sats, 25K @ 25 MHz – out-of-place, Float32, threaded | 251.0 μs | 236.0 μs | 1.06 ✅ |
| track/GPS L1CA, 1 sat, 1 signal, 5K @ 5 MHz – in-place, Float32 | 11.7 μs | 11.8 μs | 0.997 |
| track/GPS L1CA, 1 sat, 1 signal, 5K @ 5 MHz – out-of-place, Float32 | 11.7 μs | 11.7 μs | 1.0 |
| track/GPS L1CA, 1 sat, 2 signals, 5K @ 5 MHz – in-place, Float32 | 14.6 μs | 14.6 μs | 1.0 |
| track/GPS L1CA, 1 sat, 2 signals, 5K @ 5 MHz – out-of-place, Float32 | 14.3 μs | 14.3 μs | 0.996 |
| track/GPS L1CA, 1 sat, 2K @ 5 MHz – in-place, Float32 | 5.21 μs | 5.28 μs | 0.986 |
| track/GPS L1CA, 1 sat, 2K @ 5 MHz – out-of-place, Float32 | 5.2 μs | 5.11 μs | 1.02 |
| track/GPS L1CA, 1 sat, 3 signals, 5K @ 5 MHz – in-place, Float32 | 17.4 μs | 17.3 μs | 1.01 |
| track/GPS L1CA, 1 sat, 3 signals, 5K @ 5 MHz – out-of-place, Float32 | 17.1 μs | 17.0 μs | 1.01 |
| track/GPS L1CA, 1 sat, bit sync found, 2 blk @ 5 MHz – in-place, Float32 | 22.5 μs | 22.5 μs | 0.999 |
| track/GPS L1CA, 1 sat, bit sync found, 20 blk @ 5 MHz – in-place, Float32 | 221.0 μs | 221.0 μs | 1.0 |
| track/GPS L1CA, 1 sat, bit sync pending, 2 blk @ 5 MHz – in-place, Float32 | 22.6 μs | 22.6 μs | 1.0 |
| track/GPS L1CA, 1 sat, bit sync pending, 20 blk @ 5 MHz – in-place, Float32 | 222.0 μs | 222.0 μs | 1.0 |
| track/GPS L1CA, 1 sat, bit sync pending, 200 blk @ 5 MHz – in-place, Float32 | 2.29 ms | 2.29 ms | 0.999 |
| track/GPS L1CA, 8 sats, 500K @ 5 MHz – in-place, Int16 | 2.73 ms | 2.82 ms | 0.968 |
| track/GPS L1CA, 8 sats, 500K @ 5 MHz – in-place, OneBit | 1.87 ms | 1.96 ms | 0.953 |
| track/GPS L1CA, 8 sats, 500K @ 5 MHz – in-place, TwoBit | 2.79 ms | 2.62 ms | 1.06 ✅ |
| track/GPS L1CA, 8 sats, 5K @ 5 MHz – in-place, Float32 | 53.9 μs | 54.0 μs | 0.998 |
| track/GPS L1CA, 8 sats, 5K @ 5 MHz – in-place, Float32, threaded | 31.9 μs | 31.6 μs | 1.01 |
| track/GPS L1CA, 8 sats, 5K @ 5 MHz – in-place, Int16 | 32.4 μs | 32.2 μs | 1.0 |
| track/GPS L1CA, 8 sats, 5K @ 5 MHz – in-place, OneBit | 23.4 μs | 22.9 μs | 1.02 |
| track/GPS L1CA, 8 sats, 5K @ 5 MHz – out-of-place, Float32 | 54.8 μs | 54.9 μs | 0.998 |
| track/GPS L1CA, 8 sats, 5K @ 5 MHz – out-of-place, Float32, threaded | 32.9 μs | 33.3 μs | 0.988 |
| track/Galileo E1B, 4 sats, 25K @ 25 MHz – in-place, Float32 | 145.0 μs | 146.0 μs | 0.997 |
| track/Galileo E1B, 4 sats, 25K @ 25 MHz – in-place, Float32, threaded | 86.7 μs | 86.9 μs | 0.998 |
| track/Galileo E1B, 4 sats, 25K @ 25 MHz – in-place, Int16 | 68.1 μs | 67.7 μs | 1.01 |
| track/Galileo E1B, 4 sats, 25K @ 25 MHz – out-of-place, Float32 | 146.0 μs | 146.0 μs | 0.999 |
| track/Galileo E1B, 4 sats, 25K @ 25 MHz – out-of-place, Float32, threaded | 87.6 μs | 87.7 μs | 0.999 |
| time_to_load | 106.0 μs | 105.0 μs | 1.01 |
Memory benchmarks (base vs PR head)
Ratio = 7a55f91… / d71924b… (bytes allocated): >1 means the PR allocates less. ✅ ≥ 5 % less, ∞/0 mark a benchmark that drops to / picks up allocations, — means both revisions allocate nothing. A blank cell means the benchmark exists on only one revision (🆕 = new on the PR, 🗑 = removed).
| 7a55f91… | d71924b… | 7a55f91… / d71924b… | |
|---|---|---|---|
| downconvert and correlate/CPU/Float32 | 2 allocs: 528 B | 2 allocs: 528 B | 1.0 |
| downconvert and correlate/CPU/Float32 4ant | 2 allocs: 848 B | 2 allocs: 848 B | 1.0 |
| downconvert and correlate/CPU/Float64 | 2 allocs: 528 B | 2 allocs: 528 B | 1.0 |
| downconvert and correlate/CPU/Int16 | 2 allocs: 528 B | 2 allocs: 528 B | 1.0 |
| downconvert and correlate/CPU/Int16 4ant | 2 allocs: 848 B | 2 allocs: 848 B | 1.0 |
| downconvert and correlate/CPU/Int32 | 2 allocs: 528 B | 2 allocs: 528 B | 1.0 |
| fused kernel/1-ant dynamic taps | 0 allocs: 0 B | 0 allocs: 0 B | — |
| fused kernel/1-ant static taps | 0 allocs: 0 B | 0 allocs: 0 B | — |
| fused kernel/4-ant dynamic taps | 0 allocs: 0 B | 0 allocs: 0 B | — |
| fused kernel/4-ant static taps | 0 allocs: 0 B | 0 allocs: 0 B | — |
| fused tuple kernel/1-ant N=2 | 0 allocs: 0 B | 0 allocs: 0 B | — |
| fused tuple kernel/1-ant N=3 | 0 allocs: 0 B | 0 allocs: 0 B | — |
| fused tuple kernel/2-ant N=2 | 0 allocs: 0 B | 0 allocs: 0 B | — |
| fused tuple kernel/2-ant N=3 | 0 allocs: 0 B | 0 allocs: 0 B | — |
| fused tuple kernel/4-ant N=2 | 0 allocs: 0 B | 0 allocs: 0 B | — |
| fused tuple kernel/4-ant N=3 | 0 allocs: 0 B | 0 allocs: 0 B | — |
| noise estimation/append_noise_observation! – steady state | 0 allocs: 0 B | 0 allocs: 0 B | — |
| noise estimation/track! – GPS L1CA, 1 sat, 2K @ 5 MHz with a noise reference | 0 allocs: 0 B | 0 allocs: 0 B | — |
| noise estimation/update_noise! – 1 ms @ 20 MHz | 0 allocs: 0 B | 0 allocs: 0 B | — |
| noise estimation/update_noise! – 1 ms @ 4 MHz | 0 allocs: 0 B | 0 allocs: 0 B | — |
| noise estimation/update_noise! – 1 ms @ 4 MHz, 4 antennas | 0 allocs: 0 B | 0 allocs: 0 B | — |
| track/GPS L1CA + Galileo E1B, 8+8 sats, 25K @ 25 MHz – in-place, Float32 | 0 allocs: 0 B | 0 allocs: 0 B | — |
| track/GPS L1CA + Galileo E1B, 8+8 sats, 25K @ 25 MHz – in-place, Float32, threaded | 4 allocs: 384 B | 4 allocs: 384 B | 1.0 |
| track/GPS L1CA + Galileo E1B, 8+8 sats, 25K @ 25 MHz – out-of-place, Float32 | 26 allocs: 9840 B | 26 allocs: 9840 B | 1.0 |
| track/GPS L1CA + Galileo E1B, 8+8 sats, 25K @ 25 MHz – out-of-place, Float32, threaded | 30 allocs: 10224 B | 30 allocs: 10224 B | 1.0 |
| track/GPS L1CA, 1 sat, 1 signal, 5K @ 5 MHz – in-place, Float32 | 0 allocs: 0 B | 0 allocs: 0 B | — |
| track/GPS L1CA, 1 sat, 1 signal, 5K @ 5 MHz – out-of-place, Float32 | 9 allocs: 896 B | 9 allocs: 896 B | 1.0 |
| track/GPS L1CA, 1 sat, 2 signals, 5K @ 5 MHz – in-place, Float32 | 0 allocs: 0 B | 0 allocs: 0 B | — |
| track/GPS L1CA, 1 sat, 2 signals, 5K @ 5 MHz – out-of-place, Float32 | 9 allocs: 1296 B | 9 allocs: 1296 B | 1.0 |
| track/GPS L1CA, 1 sat, 2K @ 5 MHz – in-place, Float32 | 0 allocs: 0 B | 0 allocs: 0 B | — |
| track/GPS L1CA, 1 sat, 2K @ 5 MHz – out-of-place, Float32 | 9 allocs: 896 B | 9 allocs: 896 B | 1.0 |
| track/GPS L1CA, 1 sat, 3 signals, 5K @ 5 MHz – in-place, Float32 | 0 allocs: 0 B | 0 allocs: 0 B | — |
| track/GPS L1CA, 1 sat, 3 signals, 5K @ 5 MHz – out-of-place, Float32 | 9 allocs: 1648 B | 9 allocs: 1648 B | 1.0 |
| track/GPS L1CA, 1 sat, bit sync found, 2 blk @ 5 MHz – in-place, Float32 | 7 allocs: 384 B | 7 allocs: 384 B | 1.0 |
| track/GPS L1CA, 1 sat, bit sync found, 20 blk @ 5 MHz – in-place, Float32 | 7 allocs: 384 B | 7 allocs: 384 B | 1.0 |
| track/GPS L1CA, 1 sat, bit sync pending, 2 blk @ 5 MHz – in-place, Float32 | 7 allocs: 384 B | 7 allocs: 384 B | 1.0 |
| track/GPS L1CA, 1 sat, bit sync pending, 20 blk @ 5 MHz – in-place, Float32 | 7 allocs: 384 B | 7 allocs: 384 B | 1.0 |
| track/GPS L1CA, 1 sat, bit sync pending, 200 blk @ 5 MHz – in-place, Float32 | 7 allocs: 384 B | 7 allocs: 384 B | 1.0 |
| track/GPS L1CA, 8 sats, 500K @ 5 MHz – in-place, Int16 | 62 allocs: 25920 B | 62 allocs: 25920 B | 1.0 |
| track/GPS L1CA, 8 sats, 500K @ 5 MHz – in-place, OneBit | 62 allocs: 25808 B | 62 allocs: 25808 B | 1.0 |
| track/GPS L1CA, 8 sats, 500K @ 5 MHz – in-place, TwoBit | 62 allocs: 25856 B | 62 allocs: 25856 B | 1.0 |
| track/GPS L1CA, 8 sats, 5K @ 5 MHz – in-place, Float32 | 0 allocs: 0 B | 0 allocs: 0 B | — |
| track/GPS L1CA, 8 sats, 5K @ 5 MHz – in-place, Float32, threaded | 2 allocs: 192 B | 2 allocs: 192 B | 1.0 |
| track/GPS L1CA, 8 sats, 5K @ 5 MHz – in-place, Int16 | 14 allocs: 1280 B | 14 allocs: 1280 B | 1.0 |
| track/GPS L1CA, 8 sats, 5K @ 5 MHz – in-place, OneBit | 14 allocs: 1168 B | 14 allocs: 1168 B | 1.0 |
| track/GPS L1CA, 8 sats, 5K @ 5 MHz – out-of-place, Float32 | 10 allocs: 4280 B | 10 allocs: 4280 B | 1.0 |
| track/GPS L1CA, 8 sats, 5K @ 5 MHz – out-of-place, Float32, threaded | 12 allocs: 4472 B | 12 allocs: 4472 B | 1.0 |
| track/Galileo E1B, 4 sats, 25K @ 25 MHz – in-place, Float32 | 0 allocs: 0 B | 0 allocs: 0 B | — |
| track/Galileo E1B, 4 sats, 25K @ 25 MHz – in-place, Float32, threaded | 2 allocs: 192 B | 2 allocs: 192 B | 1.0 |
| track/Galileo E1B, 4 sats, 25K @ 25 MHz – in-place, Int16 | 6 allocs: 640 B | 6 allocs: 640 B | 1.0 |
| track/Galileo E1B, 4 sats, 25K @ 25 MHz – out-of-place, Float32 | 10 allocs: 2680 B | 10 allocs: 2680 B | 1.0 |
| track/Galileo E1B, 4 sats, 25K @ 25 MHz – out-of-place, Float32, threaded | 12 allocs: 2872 B | 12 allocs: 2872 B | 1.0 |
| time_to_load | 149 allocs: 11408 B | 149 allocs: 11408 B | 1.0 |
Benchmark Results (minimum time) — macos-14Reporting the minimum over all samples (robust to shared-runner contention), not the median. Alternative backends vs Float32 (
|
| Scenario | F32 | I16 | 1b | 2b | ×I16 | ×1b | ×2b |
|---|---|---|---|---|---|---|---|
| GPS L1CA, 1 sat, 3 signals @ 5 MHz | 12.1 μs | 6.08 μs | 5.03 μs | 7.79 μs | 1.99 ✅ | 2.41 ✅ | 1.56 ✅ |
| GPS L1CA, 1 sat, 4 antennas @ 5 MHz | 12.9 μs | 6.82 μs | 5.14 μs | 10.5 μs | 1.89 ✅ | 2.51 ✅ | 1.24 ✅ |
| GPS L1CA, 8 sats @ 40 MHz | 151.0 μs | 49.8 μs | 32.3 μs | 55.3 μs | 3.03 ✅ | 4.67 ✅ | 2.72 ✅ |
| GPS L1CA, 8 sats @ 5 MHz, 100 ms buffer | 2.17 ms | 893.0 μs | 662.0 μs | 987.0 μs | 2.43 ✅ | 3.28 ✅ | 2.2 ✅ |
| GPS L1CA, 8 sats @ 5 MHz | 23.4 μs | 10.8 μs | 9.25 μs | 13.5 μs | 2.16 ✅ | 2.53 ✅ | 1.73 ✅ |
| Galileo E1B, 4 sats @ 25 MHz | 109.0 μs | 44.2 μs | 2.47 ✅ | ||||
| dynamic taps @ 5 MHz (kernel) | 7.16 μs | 2.39 μs | 1.59 μs | 2.66 μs | 2.99 ✅ | 4.5 ✅ | 2.69 ✅ |
Time benchmarks (base vs PR head)
Ratio = 7a55f91… / d71924b…: >1 means the PR is faster. ✅ ≥ 5 % faster,
| 7a55f91… | d71924b… | 7a55f91… / d71924b… | |
|---|---|---|---|
| downconvert and correlate/CPU/Float32 | 2.65 μs | 2.63 μs | 1.01 |
| downconvert and correlate/CPU/Float32 4ant | 5.26 μs | 5.23 μs | 1.01 |
| downconvert and correlate/CPU/Float64 | 2.88 μs | 2.84 μs | 1.01 |
| downconvert and correlate/CPU/Int16 | 2.81 μs | 2.78 μs | 1.01 |
| downconvert and correlate/CPU/Int16 4ant | 5.2 μs | 5.19 μs | 1.0 |
| downconvert and correlate/CPU/Int32 | 2.72 μs | 2.69 μs | 1.01 |
| fused kernel/1-ant dynamic taps | 2.74 μs | 2.74 μs | 1.0 |
| fused kernel/1-ant static taps | 2.21 μs | 2.21 μs | 1.0 |
| fused kernel/4-ant dynamic taps | 8.33 μs | 8.33 μs | 1.0 |
| fused kernel/4-ant static taps | 4.76 μs | 4.74 μs | 1.0 |
| fused tuple kernel/1-ant N=2 | 3.51 μs | 3.51 μs | 1.0 |
| fused tuple kernel/1-ant N=3 | 3.65 μs | 3.65 μs | 1.0 |
| fused tuple kernel/2-ant N=2 | 6.77 μs | 6.76 μs | 1.0 |
| fused tuple kernel/2-ant N=3 | 6.02 μs | 6.02 μs | 1.0 |
| fused tuple kernel/4-ant N=2 | 12.4 μs | 12.4 μs | 1.0 |
| fused tuple kernel/4-ant N=3 | 10.8 μs | 10.8 μs | 1.0 |
| noise estimation/append_noise_observation! – steady state | 16.0 ns | 16.0 ns | 1.0 |
| noise estimation/track! – GPS L1CA, 1 sat, 2K @ 5 MHz with a noise reference | 5.6 μs | 5.64 μs | 0.994 |
| noise estimation/update_noise! – 1 ms @ 20 MHz | 22.8 μs | 22.8 μs | 1.0 |
| noise estimation/update_noise! – 1 ms @ 4 MHz | 4.95 μs | 4.96 μs | 0.999 |
| noise estimation/update_noise! – 1 ms @ 4 MHz, 4 antennas | 10.2 μs | 10.2 μs | 1.0 |
| track/GPS L1CA + Galileo E1B, 8+8 sats, 25K @ 25 MHz – in-place, Float32 | 585.0 μs | 586.0 μs | 0.999 |
| track/GPS L1CA + Galileo E1B, 8+8 sats, 25K @ 25 MHz – in-place, Float32, threaded | 228.0 μs | 228.0 μs | 1.0 |
| track/GPS L1CA + Galileo E1B, 8+8 sats, 25K @ 25 MHz – out-of-place, Float32 | 586.0 μs | 587.0 μs | 0.999 |
| track/GPS L1CA + Galileo E1B, 8+8 sats, 25K @ 25 MHz – out-of-place, Float32, threaded | 230.0 μs | 229.0 μs | 1.0 |
| track/GPS L1CA, 1 sat, 1 signal, 5K @ 5 MHz – in-place, Float32 | 13.0 μs | 13.0 μs | 1.0 |
| track/GPS L1CA, 1 sat, 1 signal, 5K @ 5 MHz – out-of-place, Float32 | 13.0 μs | 13.0 μs | 1.0 |
| track/GPS L1CA, 1 sat, 2 signals, 5K @ 5 MHz – in-place, Float32 | 17.7 μs | 17.7 μs | 1.0 |
| track/GPS L1CA, 1 sat, 2 signals, 5K @ 5 MHz – out-of-place, Float32 | 17.7 μs | 17.7 μs | 1.0 |
| track/GPS L1CA, 1 sat, 2K @ 5 MHz – in-place, Float32 | 5.6 μs | 5.62 μs | 0.996 |
| track/GPS L1CA, 1 sat, 2K @ 5 MHz – out-of-place, Float32 | 5.56 μs | 5.56 μs | 0.999 |
| track/GPS L1CA, 1 sat, 3 signals, 5K @ 5 MHz – in-place, Float32 | 19.4 μs | 19.2 μs | 1.01 |
| track/GPS L1CA, 1 sat, 3 signals, 5K @ 5 MHz – out-of-place, Float32 | 19.1 μs | 19.0 μs | 1.0 |
| track/GPS L1CA, 1 sat, bit sync found, 2 blk @ 5 MHz – in-place, Float32 | 25.6 μs | 25.5 μs | 1.0 |
| track/GPS L1CA, 1 sat, bit sync found, 20 blk @ 5 MHz – in-place, Float32 | 250.0 μs | 251.0 μs | 0.998 |
| track/GPS L1CA, 1 sat, bit sync pending, 2 blk @ 5 MHz – in-place, Float32 | 25.7 μs | 25.6 μs | 1.0 |
| track/GPS L1CA, 1 sat, bit sync pending, 20 blk @ 5 MHz – in-place, Float32 | 252.0 μs | 252.0 μs | 1.0 |
| track/GPS L1CA, 1 sat, bit sync pending, 200 blk @ 5 MHz – in-place, Float32 | 2.55 ms | 2.57 ms | 0.994 |
| track/GPS L1CA, 8 sats, 500K @ 5 MHz – in-place, Int16 | 2.27 ms | 2.28 ms | 0.996 |
| track/GPS L1CA, 8 sats, 500K @ 5 MHz – in-place, OneBit | 1.5 ms | 1.51 ms | 0.998 |
| track/GPS L1CA, 8 sats, 500K @ 5 MHz – in-place, TwoBit | 2.43 ms | 2.42 ms | 1.0 |
| track/GPS L1CA, 8 sats, 5K @ 5 MHz – in-place, Float32 | 59.4 μs | 59.8 μs | 0.994 |
| track/GPS L1CA, 8 sats, 5K @ 5 MHz – in-place, Float32, threaded | 22.0 μs | 22.0 μs | 0.998 |
| track/GPS L1CA, 8 sats, 5K @ 5 MHz – in-place, Int16 | 25.8 μs | 25.9 μs | 0.994 |
| track/GPS L1CA, 8 sats, 5K @ 5 MHz – in-place, OneBit | 18.8 μs | 18.9 μs | 0.996 |
| track/GPS L1CA, 8 sats, 5K @ 5 MHz – out-of-place, Float32 | 60.0 μs | 60.2 μs | 0.997 |
| track/GPS L1CA, 8 sats, 5K @ 5 MHz – out-of-place, Float32, threaded | 22.4 μs | 22.5 μs | 0.998 |
| track/Galileo E1B, 4 sats, 25K @ 25 MHz – in-place, Float32 | 176.0 μs | 176.0 μs | 1.0 |
| track/Galileo E1B, 4 sats, 25K @ 25 MHz – in-place, Float32, threaded | 75.4 μs | 75.4 μs | 0.999 |
| track/Galileo E1B, 4 sats, 25K @ 25 MHz – in-place, Int16 | 75.0 μs | 75.1 μs | 0.998 |
| track/Galileo E1B, 4 sats, 25K @ 25 MHz – out-of-place, Float32 | 177.0 μs | 177.0 μs | 1.0 |
| track/Galileo E1B, 4 sats, 25K @ 25 MHz – out-of-place, Float32, threaded | 104.0 μs | 104.0 μs | 1.0 |
| time_to_load | 442.0 μs | 247.0 μs | 1.79 ✅ |
Memory benchmarks (base vs PR head)
Ratio = 7a55f91… / d71924b… (bytes allocated): >1 means the PR allocates less. ✅ ≥ 5 % less, ∞/0 mark a benchmark that drops to / picks up allocations, — means both revisions allocate nothing. A blank cell means the benchmark exists on only one revision (🆕 = new on the PR, 🗑 = removed).
| 7a55f91… | d71924b… | 7a55f91… / d71924b… | |
|---|---|---|---|
| downconvert and correlate/CPU/Float32 | 2 allocs: 528 B | 2 allocs: 528 B | 1.0 |
| downconvert and correlate/CPU/Float32 4ant | 2 allocs: 848 B | 2 allocs: 848 B | 1.0 |
| downconvert and correlate/CPU/Float64 | 2 allocs: 528 B | 2 allocs: 528 B | 1.0 |
| downconvert and correlate/CPU/Int16 | 2 allocs: 528 B | 2 allocs: 528 B | 1.0 |
| downconvert and correlate/CPU/Int16 4ant | 2 allocs: 848 B | 2 allocs: 848 B | 1.0 |
| downconvert and correlate/CPU/Int32 | 2 allocs: 528 B | 2 allocs: 528 B | 1.0 |
| fused kernel/1-ant dynamic taps | 0 allocs: 0 B | 0 allocs: 0 B | — |
| fused kernel/1-ant static taps | 0 allocs: 0 B | 0 allocs: 0 B | — |
| fused kernel/4-ant dynamic taps | 0 allocs: 0 B | 0 allocs: 0 B | — |
| fused kernel/4-ant static taps | 0 allocs: 0 B | 0 allocs: 0 B | — |
| fused tuple kernel/1-ant N=2 | 0 allocs: 0 B | 0 allocs: 0 B | — |
| fused tuple kernel/1-ant N=3 | 0 allocs: 0 B | 0 allocs: 0 B | — |
| fused tuple kernel/2-ant N=2 | 0 allocs: 0 B | 0 allocs: 0 B | — |
| fused tuple kernel/2-ant N=3 | 0 allocs: 0 B | 0 allocs: 0 B | — |
| fused tuple kernel/4-ant N=2 | 0 allocs: 0 B | 0 allocs: 0 B | — |
| fused tuple kernel/4-ant N=3 | 0 allocs: 0 B | 0 allocs: 0 B | — |
| noise estimation/append_noise_observation! – steady state | 0 allocs: 0 B | 0 allocs: 0 B | — |
| noise estimation/track! – GPS L1CA, 1 sat, 2K @ 5 MHz with a noise reference | 0 allocs: 0 B | 0 allocs: 0 B | — |
| noise estimation/update_noise! – 1 ms @ 20 MHz | 0 allocs: 0 B | 0 allocs: 0 B | — |
| noise estimation/update_noise! – 1 ms @ 4 MHz | 0 allocs: 0 B | 0 allocs: 0 B | — |
| noise estimation/update_noise! – 1 ms @ 4 MHz, 4 antennas | 0 allocs: 0 B | 0 allocs: 0 B | — |
| track/GPS L1CA + Galileo E1B, 8+8 sats, 25K @ 25 MHz – in-place, Float32 | 0 allocs: 0 B | 0 allocs: 0 B | — |
| track/GPS L1CA + Galileo E1B, 8+8 sats, 25K @ 25 MHz – in-place, Float32, threaded | 4 allocs: 384 B | 4 allocs: 384 B | 1.0 |
| track/GPS L1CA + Galileo E1B, 8+8 sats, 25K @ 25 MHz – out-of-place, Float32 | 26 allocs: 10464 B | 26 allocs: 10464 B | 1.0 |
| track/GPS L1CA + Galileo E1B, 8+8 sats, 25K @ 25 MHz – out-of-place, Float32, threaded | 30 allocs: 10848 B | 30 allocs: 10848 B | 1.0 |
| track/GPS L1CA, 1 sat, 1 signal, 5K @ 5 MHz – in-place, Float32 | 0 allocs: 0 B | 0 allocs: 0 B | — |
| track/GPS L1CA, 1 sat, 1 signal, 5K @ 5 MHz – out-of-place, Float32 | 9 allocs: 896 B | 9 allocs: 896 B | 1.0 |
| track/GPS L1CA, 1 sat, 2 signals, 5K @ 5 MHz – in-place, Float32 | 0 allocs: 0 B | 0 allocs: 0 B | — |
| track/GPS L1CA, 1 sat, 2 signals, 5K @ 5 MHz – out-of-place, Float32 | 9 allocs: 1296 B | 9 allocs: 1296 B | 1.0 |
| track/GPS L1CA, 1 sat, 2K @ 5 MHz – in-place, Float32 | 0 allocs: 0 B | 0 allocs: 0 B | — |
| track/GPS L1CA, 1 sat, 2K @ 5 MHz – out-of-place, Float32 | 9 allocs: 896 B | 9 allocs: 896 B | 1.0 |
| track/GPS L1CA, 1 sat, 3 signals, 5K @ 5 MHz – in-place, Float32 | 0 allocs: 0 B | 0 allocs: 0 B | — |
| track/GPS L1CA, 1 sat, 3 signals, 5K @ 5 MHz – out-of-place, Float32 | 9 allocs: 1648 B | 9 allocs: 1648 B | 1.0 |
| track/GPS L1CA, 1 sat, bit sync found, 2 blk @ 5 MHz – in-place, Float32 | 7 allocs: 384 B | 7 allocs: 384 B | 1.0 |
| track/GPS L1CA, 1 sat, bit sync found, 20 blk @ 5 MHz – in-place, Float32 | 7 allocs: 384 B | 7 allocs: 384 B | 1.0 |
| track/GPS L1CA, 1 sat, bit sync pending, 2 blk @ 5 MHz – in-place, Float32 | 7 allocs: 384 B | 7 allocs: 384 B | 1.0 |
| track/GPS L1CA, 1 sat, bit sync pending, 20 blk @ 5 MHz – in-place, Float32 | 7 allocs: 384 B | 7 allocs: 384 B | 1.0 |
| track/GPS L1CA, 1 sat, bit sync pending, 200 blk @ 5 MHz – in-place, Float32 | 7 allocs: 384 B | 7 allocs: 384 B | 1.0 |
| track/GPS L1CA, 8 sats, 500K @ 5 MHz – in-place, Int16 | 62 allocs: 28928 B | 62 allocs: 28928 B | 1.0 |
| track/GPS L1CA, 8 sats, 500K @ 5 MHz – in-place, OneBit | 62 allocs: 28816 B | 62 allocs: 28816 B | 1.0 |
| track/GPS L1CA, 8 sats, 500K @ 5 MHz – in-place, TwoBit | 62 allocs: 28864 B | 62 allocs: 28864 B | 1.0 |
| track/GPS L1CA, 8 sats, 5K @ 5 MHz – in-place, Float32 | 0 allocs: 0 B | 0 allocs: 0 B | — |
| track/GPS L1CA, 8 sats, 5K @ 5 MHz – in-place, Float32, threaded | 2 allocs: 192 B | 2 allocs: 192 B | 1.0 |
| track/GPS L1CA, 8 sats, 5K @ 5 MHz – in-place, Int16 | 14 allocs: 1280 B | 14 allocs: 1280 B | 1.0 |
| track/GPS L1CA, 8 sats, 5K @ 5 MHz – in-place, OneBit | 14 allocs: 1168 B | 14 allocs: 1168 B | 1.0 |
| track/GPS L1CA, 8 sats, 5K @ 5 MHz – out-of-place, Float32 | 10 allocs: 4656 B | 10 allocs: 4656 B | 1.0 |
| track/GPS L1CA, 8 sats, 5K @ 5 MHz – out-of-place, Float32, threaded | 12 allocs: 4848 B | 12 allocs: 4848 B | 1.0 |
| track/Galileo E1B, 4 sats, 25K @ 25 MHz – in-place, Float32 | 0 allocs: 0 B | 0 allocs: 0 B | — |
| track/Galileo E1B, 4 sats, 25K @ 25 MHz – in-place, Float32, threaded | 2 allocs: 192 B | 2 allocs: 192 B | 1.0 |
| track/Galileo E1B, 4 sats, 25K @ 25 MHz – in-place, Int16 | 6 allocs: 640 B | 6 allocs: 640 B | 1.0 |
| track/Galileo E1B, 4 sats, 25K @ 25 MHz – out-of-place, Float32 | 10 allocs: 3056 B | 10 allocs: 3056 B | 1.0 |
| track/Galileo E1B, 4 sats, 25K @ 25 MHz – out-of-place, Float32, threaded | 12 allocs: 3248 B | 12 allocs: 3248 B | 1.0 |
| time_to_load | 200 allocs: 14176 B | 200 allocs: 14176 B | 1.0 |
|
CI follow-up after adding direct gap-handling coverage in d71924b:
I reviewed the Ubuntu and macOS benchmark comments. The bit-sync workloads affected by this PR are neutral on both platforms (roughly 1.00×), and allocations are unchanged. macOS package load improves from 442 μs to 247 μs. Ubuntu flags a 32.9 ns noise-observation microbenchmark and one threaded mixed-signal variant, but neither code path is changed here; macOS is neutral on both, and the paired Ubuntu threaded out-of-place variant moves by the same magnitude in the opposite direction. These are runner/scheduling variance rather than an actionable regression. |
Hardware correlator producers can observe elapsed signal time without having a valid prompt measurement. Add bit-clock APIs that advance those missing primary-code blocks directly, preserving bit and secondary-code phase without feeding fabricated zero correlators into discriminators, prompt filters, or C/N0 estimation.
The implementation also drains every completed bit when a record spans multiple symbol boundaries, preventing an overshoot from leaving the bit accumulator permanently stalled.
This is required by JuliaGNSS/GNSSReceiver.jl#129 and the hardware work tracked in JuliaGNSS/GNSSReceiver.jl#107.