Skip to content

Commit 9a7b38c

Browse files
committed
eve: document the module compression measurements
eve-hwe_defconfig inherits Ubuntu's `# CONFIG_MODULE_COMPRESS_ALL is not set`, so hwe ships 4429 uncompressed modules while declaring a zstd algorithm it never uses. That reads as an oversight; it is not, and the obvious one-line fix makes it worse. Measured on 6.18.35, rootfs being xz squashfs with the x86 BCJ filter: core xz on (committed) 291.97 MiB -- core xz off 292.79 MiB +0.82 hwe zstd off (committed) 417.17 MiB -- hwe zstd on 474.67 MiB +57.50 hwe xz on 418.44 MiB +1.27 Per-module compression is near-free only when it matches squashfs's own algorithm. zstd output is high-entropy, so squashfs can neither dedup nor recompress it, and its tree is bigger than xz's besides - it loses on both axes. Leaving modules raw lets squashfs-xz dedup across the whole tree, which is why uncompressed gives hwe the smallest rootfs. So setting CONFIG_MODULE_COMPRESS_ALL=y alone lands on the worst row: +57.50 MiB on every device. Both flavours stay as they are.
1 parent daa483f commit 9a7b38c

1 file changed

Lines changed: 166 additions & 0 deletions

File tree

eve/docs/module-compression.md

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
# Kernel module compression in EVE
2+
3+
**Decision: leave it exactly as it is.** `core` and `rt` compress modules with xz; `hwe` and
4+
`ai` do not compress them at all. Both are correct, and this document exists so nobody
5+
"fixes" the second one.
6+
7+
Measured 2026-08-25 on 6.18.35.
8+
9+
---
10+
11+
## Why this document exists
12+
13+
`arch/x86/configs/eve-hwe_defconfig` contains, inherited from Ubuntu's config:
14+
15+
```
16+
CONFIG_MODULE_COMPRESS=y
17+
CONFIG_MODULE_COMPRESS_ZSTD=y
18+
# CONFIG_MODULE_COMPRESS_ALL is not set
19+
```
20+
21+
`MODULE_COMPRESS_ALL` is `default y` (`kernel/module/Kconfig:384`) and is the switch that makes
22+
`modules_install` actually compress. Ubuntu turns it off explicitly, so `hwe` ships 4429
23+
uncompressed `.ko` files — 634.8 MiB — while declaring a compression algorithm it never uses.
24+
25+
That reads like an oversight. It is not, and the obvious one-line fix makes things worse.
26+
Three kernel builds and three EVE rootfs builds were spent establishing this; the numbers are
27+
below so it does not have to happen again.
28+
29+
## Results
30+
31+
EVE rootfs is squashfs with **xz**, the x86 BCJ filter and 128 KiB blocks. All rootfs figures
32+
are `installer/rootfs-generic.img` from `make HV=kvm PLATFORM=generic pkgs rootfs`.
33+
34+
| flavour | algorithm | `MODULE_COMPRESS_ALL` | modules | module tree in image | rootfs.img | Δ rootfs |
35+
|---|---|---|---:|---:|---:|---:|
36+
| **core** | xz | **on** — committed | 297 | 16.4 MiB | **291.97 MiB** ||
37+
| core | xz | off | 297 | 91.4 MiB | 292.79 MiB | +0.82 MiB |
38+
| **hwe** | zstd | **off** — committed | 4429 | 634.8 MiB | **417.17 MiB** ||
39+
| hwe | zstd | on | 4429 | 196.7 MiB | 474.67 MiB | **+57.50 MiB** |
40+
| hwe | xz | on | 4429 | 143.7 MiB | 418.44 MiB | +1.27 MiB |
41+
42+
## The mechanism
43+
44+
Per-module compression is nearly free on the rootfs **only when it uses the same algorithm as
45+
squashfs**. EVE's squashfs is xz, so:
46+
47+
- **xz modules** cost +0.82 MiB (core) and +1.27 MiB (hwe). Squashfs loses its dedup and
48+
cross-file window over that data, but per-file xz with a larger dictionary recovers almost
49+
all of it.
50+
- **zstd modules** cost **+57.50 MiB**. The output is high-entropy, so squashfs can neither
51+
dedup nor recompress it, and zstd's own ratio is worse than what squashfs-xz achieves over
52+
the raw tree. It loses on both axes at once — its module tree (196.7 MiB) is also bigger
53+
than xz's (143.7 MiB).
54+
55+
Leaving modules **uncompressed** lets squashfs-xz work across the whole 634.8 MiB tree with
56+
dedup and a shared dictionary, which is why it produces the smallest rootfs of the three.
57+
58+
## The trap
59+
60+
The tempting change is one line in `common.fragment`:
61+
62+
```
63+
CONFIG_MODULE_COMPRESS_ALL=y # DO NOT
64+
```
65+
66+
Because `eve-hwe_defconfig` selects zstd, that lands on the **worst row in the table**:
67+
+57.50 MiB on every device's rootfs. If you want compression on `hwe` you must also switch the
68+
algorithm to xz — and even then it costs +1.27 MiB of rootfs and roughly 10-18 minutes of
69+
build time, to save container-image size.
70+
71+
## Why `hwe` stays uncompressed
72+
73+
Uncompressed wins on the two things that affect deployed devices:
74+
75+
- **smallest rootfs** — 417.17 MiB, better than either compressed variant
76+
- **fastest module load** — one decompression pass instead of two
77+
78+
It loses only on **container image size**: 634.8 MiB of modules versus 143.7 MiB with xz, so
79+
roughly 491 MiB more per image pulled, cached and stored. That is a CI and registry cost, not
80+
a device cost, and EVE optimises for the device.
81+
82+
## Why `core` stays compressed
83+
84+
`core` serves eve-kvm under a hard rootfs cap (`ROOTFS_MAXSIZE_MB`, 290 historically and 295
85+
after the resize work), and xz compression makes its rootfs *smaller*, not larger. It is
86+
already `default y` because `eve-core_defconfig` does not carry Ubuntu's opt-out. Nothing to
87+
do.
88+
89+
For reference, `core` on 6.18.35 measures 291.97 MiB against a shipping 6.12 kvm rootfs of
90+
290.13 MiB — it clears the 295 cap and misses the 290 one.
91+
92+
## Costs, measured
93+
94+
**Build time** (kernel + EVE rootfs, same machine):
95+
96+
| | |
97+
|---|---|
98+
| hwe, no compression | 33 min *(ccache cold)* |
99+
| hwe, zstd | 43 min *(ccache warm)* |
100+
| hwe, xz | 51 min *(ccache warm)* |
101+
102+
Compression is added work at `modules_install` and it is not free at 4429 modules.
103+
104+
**Module load** — decompressing all 297 `core` modules, 16.4 MB → 91.4 MB, took 3.75 s
105+
single-threaded including per-file process spawn (an upper bound; in-kernel decompression
106+
avoids that). The two passes are not equal in size:
107+
108+
| | squashfs pass | module pass |
109+
|---|---|---|
110+
| uncompressed | decompresses 91.4 MB of output ||
111+
| compressed | decompresses 16.4 MB of output | 16.4 → 91.4 MB |
112+
113+
Squashfs has 5.6× less to do when modules arrive pre-compressed, so the module pass largely
114+
*replaces* work rather than adding to it — the net penalty is about **+16%**, not +100%. And
115+
a real device loads a few dozen modules, not all of them.
116+
117+
## Reproducing this
118+
119+
No push is needed; linuxkit resolves a locally built kernel from `~/.linuxkit/cache`.
120+
121+
```sh
122+
# build a kernel flavour
123+
cd eve-kernel
124+
make -f Makefile.eve KERNEL_CONFIG_FLAVOR=<core|hwe> kernel-gcc
125+
126+
# build an EVE rootfs against it
127+
cd ../eve
128+
make KERNEL_TAG=$(make -C ../eve-kernel -s -f Makefile.eve \
129+
KERNEL_CONFIG_FLAVOR=<core|hwe> docker-tag-gcc) \
130+
HV=kvm PLATFORM=generic pkgs rootfs
131+
```
132+
133+
Two things that will otherwise waste your time:
134+
135+
- **`pkgs` before `rootfs` is required** if your eve checkout has local modifications. Packages
136+
are addressed by source hash, so a modified `pkg/pillar` hashes to a tag that exists in no
137+
registry and the pull fails.
138+
- **The rootfs size check runs *after* the image is written** (`eve/Makefile:935`), so a build
139+
that exceeds `ROOTFS_MAXSIZE_MB` still leaves a measurable `rootfs-generic.img`. You do not
140+
need to raise the cap to measure something over it.
141+
142+
To vary compression, append to `arch/x86/configs/common.fragment` (uncommitted — the kernel
143+
then tags `-dirty` and cannot collide with a clean build):
144+
145+
```
146+
CONFIG_MODULE_COMPRESS_XZ=y # or leave the flavour's own choice
147+
CONFIG_MODULE_COMPRESS_ALL=y
148+
```
149+
150+
A quick synthetic check, useful before spending a full build: `mksquashfs` the module tree both
151+
ways with EVE's real settings. It predicted core's result to within 30 KB.
152+
153+
```sh
154+
mksquashfs <tree> out.sqfs -comp xz -Xbcj x86 -b 131072 -no-progress -quiet
155+
```
156+
157+
## When to revisit
158+
159+
- **The rootfs compressor changes.** The whole result turns on modules and squashfs using the
160+
same algorithm. If EVE's squashfs moves to zstd, the conclusions inverts — zstd modules
161+
become the cheap option and xz the expensive one.
162+
- **Container image size starts to matter more than device size** — for example if image pulls
163+
dominate a deployment or CI cost, `hwe` with **xz** (not zstd) buys ~491 MiB per image for
164+
+1.27 MiB of rootfs.
165+
- **`core` approaches its cap.** It is already within ~3 MiB of the 295 MiB limit, and
166+
compression is already on, so there is no headroom left to buy here.

0 commit comments

Comments
 (0)