Summary
GPSL5I builds its 10230×37 code matrix at construction via gen_l5i_code per PRN. Profiling (done while implementing Galileo E5a, which uses a similar LFSR generator) shows two independent optimization opportunities in src/gps/l5i.jl:
mapreduce(…, hcat, 1:37) accumulation in read_gpsl5i_codes — modest time cost, large allocation cost.
gen_l5i_code internals — the actual dominant cost.
Neither is on a runtime hot path (gen_code!/get_code operate on the prebuilt matrix); this only affects one-time construction. Filing for visibility — not urgent.
Measurements (read_gpsl5i_codes, 37 PRNs, steady-state median)
| variant |
time |
allocations |
current mapreduce(…, hcat, …) |
3.45 ms |
7.57 MB |
| preallocated column-fill |
3.03 ms |
0.76 MB |
| gain |
1.14× (~0.42 ms) |
10× (−6.8 MB) |
So the hcat accumulation (which reallocates a growing matrix 37 times) leaves only ~0.42 ms / ~12% on the table time-wise, but ~6.8 MB of needless allocation churn.
The bigger opportunity: gen_l5i_code
The hcat is not the bottleneck — gen_l5i_code is. Pure LFSR work is ~3.0 ms (37 × ~81 µs/PRN). For contrast, the new Galileo E5a generator produces 50 PRNs in ~0.7 ms, while L5-I produces 37 in ~3 ms — roughly 5–6× slower per code.
Likely causes in gen_l5i_code / shift_register:
- Per-call allocation of tap-index arrays (
[9, 10, 12, 13], [1, 3, 4, 6, 7, 8, 12, 13]) and the weight vector [4096, …, 1].
- Array/index-based shift register instead of bitmask taps.
The E5a generator (src/galileo/e5a.jl, _e5_lfsr_bits) uses bitmask taps with count_ones for the feedback parity, which is much faster. Porting L5-I to the same style would recover most of the ~2.5 ms.
Suggested fix
- Preallocate the matrix in
read_gpsl5i_codes and fill columns in place (10× allocation drop, ~0.4 ms).
- Rewrite
gen_l5i_code/shift_register to a bitmask LFSR (hoist the constant tap masks out of the loop), mirroring _e5_lfsr_bits in e5a.jl.
Notes
- Found while adding Galileo E5a; deliberately left out of that change to keep it focused.
- Measured on Julia 1.12,
--project, steady-state median of 11 runs (first-call compilation excluded).
Summary
GPSL5Ibuilds its 10230×37 code matrix at construction viagen_l5i_codeper PRN. Profiling (done while implementing Galileo E5a, which uses a similar LFSR generator) shows two independent optimization opportunities insrc/gps/l5i.jl:mapreduce(…, hcat, 1:37)accumulation inread_gpsl5i_codes— modest time cost, large allocation cost.gen_l5i_codeinternals — the actual dominant cost.Neither is on a runtime hot path (
gen_code!/get_codeoperate on the prebuilt matrix); this only affects one-time construction. Filing for visibility — not urgent.Measurements (
read_gpsl5i_codes, 37 PRNs, steady-state median)mapreduce(…, hcat, …)So the
hcataccumulation (which reallocates a growing matrix 37 times) leaves only ~0.42 ms / ~12% on the table time-wise, but ~6.8 MB of needless allocation churn.The bigger opportunity:
gen_l5i_codeThe
hcatis not the bottleneck —gen_l5i_codeis. Pure LFSR work is ~3.0 ms (37 × ~81 µs/PRN). For contrast, the new Galileo E5a generator produces 50 PRNs in ~0.7 ms, while L5-I produces 37 in ~3 ms — roughly 5–6× slower per code.Likely causes in
gen_l5i_code/shift_register:[9, 10, 12, 13],[1, 3, 4, 6, 7, 8, 12, 13]) and the weight vector[4096, …, 1].The E5a generator (
src/galileo/e5a.jl,_e5_lfsr_bits) uses bitmask taps withcount_onesfor the feedback parity, which is much faster. Porting L5-I to the same style would recover most of the ~2.5 ms.Suggested fix
read_gpsl5i_codesand fill columns in place (10× allocation drop, ~0.4 ms).gen_l5i_code/shift_registerto a bitmask LFSR (hoist the constant tap masks out of the loop), mirroring_e5_lfsr_bitsine5a.jl.Notes
--project, steady-state median of 11 runs (first-call compilation excluded).