perf(os/mkosi): make cached builds reuse what has not changed - #834
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A cached mkosi build repeated work whose inputs had not changed. Profiling a
warm build — every component restored from cache, so the only real work left
was assembly — put 498 s on the clock:
mkosi.finalize— squashfs compressionmkosi.postoutput— release tarballsmkosi.build— 9 components, all cache hitsEach has a separate cause, and none of them is the component cache failing:
-processors 1.beside them — a redistributable an iteration build does not want.
lived in the per-build sandbox. The kernel archive alone unpacks 6.4 GiB.
because
CARGO_TARGET_DIRandCARGO_HOMEwere in that sandbox too.Fix
Five optimizations, each measured separately, plus the fixes for what they
broke on the way. The shape is the same throughout: work whose inputs have not
changed should not be redone, and anything persisted must carry a key that
actually invalidates it.
--archiveopts back in)Incremental=CARGO_HOMEResults
Verification
Two gates, both run on the final tree.
repro-check's leg a is a cold,archiving build, so it doubles as the reference for the byte comparison rather
than paying for a separate cold build.
Cached and cold builds agree byte for byte — 15/15 artifacts:
repro-checkpasses, its two legs deliberately running with different jobcounts (32 vs 16) so a parallelism-dependent output fails rather than hides.
This gate ordering matters:
repro-checkforcescache=0and therefore neverexecutes the cached path at all. Every change here is confined to that path, so
repro-checkwould pass even if the cache were producing garbage. The bytecomparison is the only gate that covers them.
Each new cache also carries a falsification test — a change it could hide must
still be visible in the resulting image:
mkosi.skeleton/appears in the built rootfsand a staging tree deleted behind the stamp's back is re-extracted rather
than skipped (both pinned in
test-dev-cache.sh)Notes for review
Incremental=could not be enabled as-is. mkosi keys it on&d~&r~&a~&I,whose specifier set contains no digest of
Packages=ormkosi.skeleton/.Editing either would have left the key untouched and silently restored a stale
tree, so a digest of those inputs is folded into
CacheKey=.Persisting
CARGO_HOMEmoved the dependency git checkouts, whose paths rustcembeds in the binaries. Without remapping them the cached build's binaries
differed from the cold build's — and only inside the binaries, with every path
that was remapped looking identical. The remap now applies to both layouts so
they land on the same string; this changes the absolute path embedded in
release binaries from
/var/tmp/dstack-cargo-hometo/usr/src/dstack-cargo-home,which is a one-time change to the artifact bytes.
Three commits here fix problems the persistence changes introduced: the kernel
staging tree is a merge target rather than a cache and collides with itself
when persisted; the component work directory holds git checkouts that must
start empty; and the staged marker has to live where the per-build wipe cannot
reach it. They are kept as separate commits because each documents a distinct
reason a directory is or is not safe to persist.
Not achieved: the 60 s target for an edited rebuild. 131 s breaks down as
cargo 43 s, kernel re-extraction 17 s, and 68 s of fixed cost — finalize's
squashfs and verity, postoutput's partitioned image, four measurements and the
UKI, plus mkosi's own startup. Even with cargo and the kernel at zero that
fixed cost exceeds 60 s. Reaching it would mean not producing a complete
measurable image on every iteration, which is a different feature rather than a
further optimization.