Release Notes — v0.7.9¶
Memory footprint: lazy allocation of the RTCM local-correction block¶
Release date: 2026-08-25 Type: Performance (memory footprint) — no positioning change Branch: release/v0.7.9
Overview¶
A repository memory audit and production measurements from a containerized CLAS deployment (#295) showed that every mrtk run solver process was ~1.2 GB resident at startup — essentially ~12 copies of rtcm_t. The cause: rtcm_t embedded the MADOCA local-correction block lclblock_t (~307 MB, 97.6 % of the struct, dominated by siteion_t istat[MAXBLK][MAXTRPSTA]) by value. rtksvr_t carries three rtcm_t, each stream converter (strconv_t) two more — yet the block is only ever touched by the local-correction feature (RTCM3 message types 2001–2016). Every normal rover/base/PPP/RTK/PPP-RTK run paid the footprint for a feature it never used.
Fix¶
PR #323 converts the member to a lazily heap-allocated pointer:
rtcm_lclblk()allocates the block on first use; the type-2001–2016 decoders call it on entry.- Encoders and
block2stat()treat a missing block exactly like the legacy all-zeros state (NULL guard, same return values);free_rtcm()releases it. init_rtcm()intentionally leaves the pointer untouched — the struct must be zero-initialized before the first call (all in-tree callers use calloc/memset), and an existing allocation is preserved across re-init to match the legacy embedded-array semantics.- 46 mechanical
.→->migrations; outside the inserted guards the code is byte-identical (verified by reverse-rewrite diff). - On the way, a latent bug:
strconvnew()allocatedstrconv_twithmalloc, sostrconvfree()freed an uninitializedlclblkpointer. Nowcalloc(required by the ownership contract).
Results¶
Measured with the default preset (NFREQ=5, all constellations, MAXSAT=221):
| Before | After | |
|---|---|---|
rtcm_t | 314.4 MB | 7.5 MB |
rtksvr_t | 971.7 MB | 51.1 MB |
strconv_t | 635.8 MB | 22.1 MB |
mrtk run startup peak RSS (RT replay, macOS) | 808 MiB | 84 MiB (−89.5 %) |
Memory stops being the binding constraint on solver count per host: the three-CLAS-solver deployment that motivated #295 needed an 8 GB machine; after the fix the same workload fits in well under 1 GB.
Validation¶
- Full regression gate: 121 tests, 120 pass; the single failure is the documented environment-only
madocalib_pppar_ion_check(LAPACK-vs-reference ~1.6 cm vs 0.5 cm tolerance, reproduces on clean develop). RT CLAS replays pass at their recorded ~371 s in isolation. - New
utest_lclblk(30 checks): fresh-struct invariants (NULL block, guarded consumers, idempotent allocation, double-free safety) plus a type-2001 encode→decode round-trip throughgen_rtcm3()/input_rtcm3()— the first direct coverage of the 2001–2016 path. - Post-processing output equivalence: before/after
.posdeltas are indistinguishable from same-binary rerun noise (macOS Accelerate FP nondeterminism); code-level identity is established by the reverse-rewrite diff.
Compatibility¶
- External embedders of
rtcm_tmust rebuild (struct layout change). Any out-of-tree code that declares its ownrtcm_tmust zero-initialize it before the firstinit_rtcm()call (all in-tree callers already did). - Behavior note: decoded local-correction blocks no longer survive an
rtksvrstop/start cycle. The legacy code kept the stale array and re-stamped stale blocks with the current time on the nextblock2stat()— arguably a defect; fresh corrections repopulate the block on arrival. - Upstream syncs: MALIB still embeds
lclblkby value, so hunks cherry-picked fromrtcm3lcl.c/lclcmn.c/lclcmbcmn.cneed the member-access and allocation-guard adaptation — seedocs/dev/pitfalls-public.mdP-13.