Memory-optimization measurements: full branch vs audited subset
Public edition, 2026-07-20. This is a trimmed copy of an internal report: internal cross-references and the analysis of one unrelated defect (tracked separately) are removed. All measurements, procedure details, and provenance identifiers are unchanged.
What was measured, and why
SoftAtHome's memory-optimization work for prplware was delivered as a set of component branches based on prplOS 4.2.0. In a separate commit audit, every one of the branch set's 1,037 commits was classified twice by independent reviewers, with each disagreement settled against the full diff. The audit's conclusion: 117 component commits (90 memory optimizations plus 27 build and API dependencies) and 9 prplOS-side commits carry the actual memory work, while the other 920 commits are unrelated features, CI changes, and packaging churn.
This report answers the follow-up question: do images built from only those audited commits (the "surgical" subset) deliver the same memory savings as images built from the full branches? The comparison ran on two bases: prplOS 4.2.0, where the optimization branches originated, and current latest-24.10.
Headline results
On 4.2.0, the subset delivers the full branch's savings. Vanilla (M0) runs at 178,514 KiB of userspace memory; the full branch (M1) reduces that by 9,551 KiB, and the subset (M2) by 9,943 KiB, which is 104.1% of the full branch's reduction. The small overshoot is within the per-boot spreads and is not claimed as a win on its own.
On latest-24.10, the subset beats the full branch. The subset (M4) reduces memory by 5,146 KiB against vanilla (M3); the full branch (M5) reduces it by only 1,631 KiB, leaving the full-branch image 3,515 KiB heavier than the subset image. On this base, the commits the audit classified as unrelated cost memory rather than saving it. One caveat applies to M5 and is noted in the process comparison below.
How the measurements were taken
Every image ran in the same QEMU/KVM virtual machine with 512 MiB of RAM, booted three times, with 180 seconds of settle time before capture. Two metrics were recorded:
- Userspace PSS: the sum of every userspace process's proportional set size, i.e. its resident memory with shared pages split evenly between the processes that use them. Lower is better.
- MemAvailable: the kernel's estimate of memory available to new workloads before swapping. Higher is better.
All figures are medians over the three boots, with the spread between the best and worst boot alongside. The measurement procedure, test list, and virtual-machine shape were identical, byte for byte, at all six points.
One acquisition note: the CI job for M1 (14922716187) failed because a source tag had been deleted upstream, so M1 was rebuilt locally from that job's retained CI configuration, which was verified byte-identical.
| Point | Description | Userspace PSS median KiB | Spread | MemAvailable median KiB | Spread | Image bytes | SquashFS bytes |
|---|---|---|---|---|---|---|---|
| M0 | vanilla 4.2.0 | 178,514 | 1,590 | 237,872 | 1,592 | 174,358,548 | 31,185,344 |
| M1 | full branch (locally built substitute), 4.2.0 | 168,963 | 3,905 | 247,096 | 5,100 | 174,358,548 | 31,285,428 |
| M2 | surgical subset, 4.2.0 | 168,571 | 1,514 | 247,912 | 1,792 | 174,358,548 | 31,184,818 |
| M3 | vanilla latest-24.10 | 183,492 | 2,184 | 236,428 | 2,188 | 174,358,548 | 33,159,492 |
| M5 | full branch, latest-24.10, CI job 15421624536 | 181,861 | 294 | 237,272 | 796 | 174,358,548 | 33,231,680 |
| M4 | surgical subset, latest-24.10 | 178,346 | 396 | 240,496 | 1,264 | 174,358,548 | 33,163,708 |
| Comparison | PSS delta KiB | MemAvailable delta KiB | Interpretation |
|---|---|---|---|
| M1 − M0 | -9,551 | +9,224 | full-branch reduction on 4.2.0 |
| M2 − M0 | -9,943 | +10,040 | surgical-subset reduction on 4.2.0 |
| M2 − M1 | -392 | +816 | subset and full branch are effectively at parity |
| M5 − M3 | -1,631 | +844 | full-branch reduction on latest-24.10 |
| M4 − M3 | -5,146 | +4,068 | surgical-subset reduction on latest-24.10 |
| M4 − M5 | -3,515 | +3,224 | subset vs full branch, directly, on latest-24.10 |
The other median meminfo values were:
| Point | MemFree KiB | Cached KiB | Slab KiB |
|---|---|---|---|
| M0 | 40,628 | 198,988 | 61,268 |
| M1 | 49,744 | 198,720 | 62,028 |
| M2 | 51,252 | 198,648 | 61,380 |
| M3 | 27,124 | 207,780 | 63,008 |
| M5 | 28,284 | 207,808 | 63,620 |
| M4 | 32,416 | 207,448 | 63,108 |
Per-process comparison on 4.2.0
The totals break down by process as follows, using the subset image's top 20 processes by memory. Deltas are signed KiB; negative means less memory used.
| Process | M0 | M1 | M2 | M1−M0 | M2−M0 | M2−M1 |
|---|---|---|---|---|---|---|
unbound |
10,431 | 8,437 | 10,467 | -1,994 | +36 | +2,030 |
beerocks_contro |
9,114 | 9,212 | 8,962 | +98 | -152 | -250 |
beerocks_agent |
8,158 | 8,164 | 8,119 | +6 | -39 | -45 |
obuspa |
7,905 | 7,860 | 7,821 | -45 | -84 | -39 |
ubusd |
8,040 | 7,711 | 7,655 | -329 | -385 | -56 |
syslog-ng |
6,514 | 6,570 | 6,510 | +56 | -4 | -60 |
wld |
5,743 | 5,605 | 5,568 | -138 | -175 | -37 |
tr181-firewall |
9,137 | 5,545 | 5,541 | -3,592 | -3,596 | -4 |
cthulhu |
5,195 | 5,082 | 4,983 | -113 | -212 | -99 |
beerocks_fronth |
4,642 | 4,554 | 4,578 | -88 | -64 | +24 |
netmodel |
3,476 | 3,477 | 3,404 | +1 | -72 | -73 |
netmodel-client |
4,641 | 3,474 | 3,353 | -1,167 | -1,288 | -121 |
beerocks_vendor |
2,855 | 2,862 | 2,870 | +7 | +15 | +8 |
ip-manager |
2,929 | 2,601 | 2,568 | -328 | -361 | -33 |
deviceinfo-mana |
2,477 | 2,507 | 2,504 | +30 | +27 | -3 |
ieee1905_transp |
2,399 | 2,432 | 2,444 | +33 | +45 | +12 |
pcm-manager |
2,138 | 2,137 | 2,147 | -1 | +9 | +10 |
tr181-upnpdisco |
2,032 | 2,021 | 2,006 | -11 | -26 | -15 |
lighttpd |
1,977 | 1,988 | 1,988 | +11 | +11 | 0 |
tr181-ipdiagnos |
1,967 | 1,874 | 1,890 | -93 | -77 | +16 |
The firewall and netmodel-client reductions reproduce or slightly exceed the full branch, and the rest of the subset's top 20 stays close to it. Two larger differences have known causes in how the subset was selected:
unboundis 2,030 KiB above M1 because the Unbound work is a separate track that was not part of the audited optimization set (the Confluence backport inventory also lists it separately). The subset image keeps the vanilla Unbound footprint.gmap-clientis 950 KiB above M1 (it falls outside the top-20 table). The audit kept no gmap-client commits, because that component's changes on the optimization branch were feature, startup, or packaging work rather than steady-state memory savings. The subset image keeps the vanilla 4.2.0 process shape instead of importing them.
Per-process comparison on latest-24.10
The same breakdown for latest-24.10: vanilla M3, full branch M5, subset M4, and all three deltas.
| Process | M3 KiB | M5 KiB | M4 KiB | M5−M3 KiB | M4−M3 KiB | M4−M5 KiB |
|---|---|---|---|---|---|---|
beerocks_contro |
9,449 | 9,521 | 9,416 | +72 | -33 | -105 |
obuspa |
9,329 | 9,409 | 9,264 | +80 | -65 | -145 |
unbound |
8,454 | 8,432 | 8,441 | -22 | -13 | +9 |
ubusd |
8,571 | 8,255 | 8,223 | -316 | -348 | -32 |
beerocks_agent |
8,120 | 8,221 | 8,213 | +101 | +93 | -8 |
syslog-ng |
6,205 | 6,237 | 6,207 | +32 | +2 | -30 |
wld |
6,146 | 6,151 | 6,155 | +5 | +9 | +4 |
tr181-firewall |
8,149 | 5,745 | 5,753 | -2,404 | -2,396 | +8 |
cthulhu |
5,070 | 5,186 | 5,159 | +116 | +89 | -27 |
rlyeh |
4,957 | 4,934 | 4,848 | -23 | -109 | -86 |
beerocks_fronth |
4,572 | 4,519 | 4,536 | -53 | -36 | +17 |
netmodel |
3,459 | 3,528 | 3,444 | +69 | -15 | -84 |
netmodel-client |
4,525 | 3,358 | 3,389 | -1,167 | -1,136 | +31 |
tr181-security |
3,150 | 3,163 | 3,172 | +13 | +22 | +9 |
beerocks_vendor |
2,735 | 2,754 | 2,798 | +19 | +63 | +44 |
deviceinfo-mana |
2,673 | 2,734 | 2,705 | +61 | +32 | -29 |
ip-manager |
2,673 | 2,694 | 2,646 | +21 | -27 | -48 |
pcm-manager |
2,322 | 2,334 | 2,354 | +12 | +32 | +20 |
ieee1905_transp |
2,354 | 2,402 | 2,350 | +48 | -4 | -52 |
tr181-upnpdisco |
2,079 | 2,142 | 2,130 | +63 | +51 | -12 |
The dominant reductions are shared by the full branch and the subset:
firewall (-2,404 and -2,396 KiB), netmodel-client (-1,167 and -1,136 KiB),
and ubusd (-316 and -348 KiB). Their direct M4−M5 deltas are only +8, +31,
and -32 KiB. M4 is also 139 KiB below M5 in gmap-server and 26 KiB below
in hosts-manager, matching the selected gMap and hosts work.
tr181-device (-312 KiB) and tr181-dynamicdns (-200 KiB) are the largest
other M4−M5 reductions; both have retained accessor-conversion commits in
the subset.
Two comparisons need explicit qualification:
unboundis effectively identical in M5 and M4 (+9 KiB). The audit excluded the separate Unbound track, and a later source review found the full branch's mod-dns-unbound pin content-free or already upstream, so there is no latest-24.10 Unbound penalty to explain.gmap-clientis structured differently on the two sides (the full branch splits it into separate module processes), and the M5 side of the comparison carries an unrelated service-registration defect, tracked separately. The per-row delta therefore understates rather than overstates the subset's result. This is the caveat referenced in the headline results.
Because the full branch reduces latest-24.10 memory by only 1,631 KiB while the subset reduces it by 5,146 KiB, a "share of the reduction recovered" figure computes to 315.5% and is not meaningful on this base; it is recorded as informational only. The direct comparison stands: the subset image is 3,515 KiB lighter in PSS and 3,224 KiB better in MemAvailable than the full-branch image.
Functional test parity
Alongside the memory numbers, every image ran the same seven functional tests from the prplOS cram suite (cram replays a recorded shell session and diffs the output). These runs check parity: an optimized image must not change any test result relative to its own vanilla baseline. Absolute pass rates are a property of the baselines and of the reduced test environment described below, not of the optimization work; both baselines fail several of these tests, and what matters is that the optimized images fail exactly the same ones, in the same way. Empty output or a missing or invalid test-result file counts as a harness error, and the results below come from parsed xUnit files.
| Point | Run | Primary pass/fail | Post pass/fail | Harness errors | Parity result |
|---|---|---|---|---|---|
| M0 | cram-20260719T175922Z |
0 / 6 | 0 / 1 | 0 | adopted 4.2.0 baseline; determinism proven by two byte-identical runs |
| M1 | cram-20260719T173323Z |
0 / 6 | 0 / 1 | 0 | same test-status set as M0 |
| M2 | cram-20260719T213230Z |
0 / 6 | 0 / 1 | 0 | same set as M0/M1; no pass regressed |
| M3 | cram-20260719T181657Z |
1 / 5 | 0 / 1 | 0 | adopted latest-24.10 baseline; obuspa.t passes |
| M5 | cram-20260720T081040Z |
1 / 5 | 0 / 1 | 0 | same test-status set as M3/M4 |
| M4 | cram-20260719T232851Z |
1 / 5 | 0 / 1 | 0 | same set as M3; obuspa.t remains passing |
One correction was needed along the way: the first M4 test run showed one extra failure because the chosen prplOS test fixture still contained an expectation line produced by a feature commit the audit had dropped. That single line was removed, the rebuilt image came out byte-identical, and the final run above matches M3 exactly. No expectation belonging to selected behavior was weakened.
All accepted runs of the subset images reached steady state with dropbear,
ubusd, amxrt, and the Ambiorix plugins running; M4 additionally shows
gmap-server running. The captured evidence scans show no oops, kernel panic,
out-of-memory kill, or segmentation fault on any accepted subset run. The
post-test failure and nine cthulhu assertion messages appear identically
in the vanilla baselines; they are pre-existing behavior, not something the
subset introduced.
The test environment is QEMU user-mode networking with a single NIC, so the
interfaces eth1, br-guest, and br-lcm do not exist in the guest.
Firewall, QoS, and the post test therefore run with reduced scope, and their
value here is output identity between baseline and optimized images rather
than full functional coverage. This limitation applies equally at all six
points.
Dependency and adaptation ledger
Building the subset surfaced a handful of commits that the audited picks need but that the audit had not selected, for example an API function a retained commit calls. Every such addition is listed here with its reason, so the subset's exact content stays fully accounted for and the set could not grow quietly.
| Base | Item | Kind | Reason and scope |
|---|---|---|---|
| 4.2.0 | libamxd flag storage 07e50d86e068 |
exact semantic dependency, adapted | supplies the set-backed flags required by retained accessors; folds only the required header half of its companion |
| 4.2.0 | libamxo flag serialization 9a0e036679d4 |
exact dependency | serializes the set-backed flags consumed from libamxd |
| 4.2.0 | libamxc amxc_var_is_empty 9382ac5d16ee |
exact dependency | supplies the API used by retained router-advertisement code |
| 4.2.0 | hosts-manager opaque sync context | authored narrow adaptation | pointer/new/delete conversion preserves alternative-device synchronization |
| 4.2.0 | gmap generated install rule ae42705f9503 |
exact generated companion | installs debug_definition.odl; without it gmap-server crash-looped, so the first image was rejected |
| latest-24.10 | libamxc amxc_var_is_empty 9382ac5d16ee |
exact dependency | pinned v2.7.1 lacks the API required by the retained router-advertisement pick |
| latest-24.10 | hosts-manager opaque sync context | authored narrow adaptation | pointer/new/delete conversion avoids importing unrelated feature removal |
| latest-24.10 | gmap generated install rule ae42705f9503 |
exact generated companion | keeps generated installation in sync with the selected debug model |
Every item has exact commits, build evidence, and rationale in the internal campaign records. Images from the intermediate failed builds were never measured.
Cross-check against the backport inventory
Confluence page 2238382515 (version 9, "2026-07-10: Memory (Backports, Future Enhancements)") lists the intended backport areas. It was fetched read-only after the subset was frozen and used as a completeness cross-check, not as a source of new commits.
| Inventory area | Surgical coverage |
|---|---|
| Ambiorix/generic model and accessor work | covered across the selected libraries, modules, and plugins |
| Firewall and QoS | covered, including required dependency/fix chains |
| gMap and hosts | covered; generated-file and opaque-context ledger items documented |
| PCM and UPnP | covered by selected component commits |
| DHCP, WAN, dynamic DNS configuration | covered by selected managers/plugins |
| Unbound, ZRAM | not covered; separate future tracks |
| prplMesh and profile-level reductions | not covered by this frozen campaign |
| syslog-ng, OB-USPA, CPU, data-model profiles | not covered; future/separately scoped work |
Provenance and limitations
- M0 and M3 are vanilla artifacts as released. M5 is the full-branch
latest-24.10 artifact from successful CI job 15421624536 at revision
9093d9434baf546930f12e1631072301e232d51f. M1 is the locally built substitute for failed CI job 14922716187, built from that job's retained, byte-identical CI configuration and the source of the upstream-deleted libsahtrace tag. - M2 and M4 are local builds from pinned prplOS and feed bases. All source overrides resolve to their recorded manifests. moca-manager is not part of the x86 image, so its override was verified through its prepare step on both bases.
- The subset is the keep verdict of the dual independent commit audit: 117 component commits (90 direct memory optimizations plus 27 infrastructure dependencies) and 9 prplOS-side commits.
- All numbers are three-boot medians with spreads, measured in one specific virtual-machine shape; they support the comparisons made here and are not universal performance claims. No numeric pass/fail threshold on memory was set for this work.