perf(cef_host): drop Chromium's spare renderer by default (~23 MB per profile) - #29
Merged
Merged
Conversation
… profile)
Chromium keeps one pre-warmed spare renderer per profile so the next
navigation can skip process startup. It is why renderer count measures
N+1 at EVERY N — including N=1, where a board with a single tile pays for
a second renderer it never uses.
Measured on an always-live bench (N mounted CefWebViews, no canvas, no
lazy-spawn gate), phys_footprint via `top -stats mem`, shared profile,
N in {1,2,4,8,16}:
renderers N+1 -> exactly N at every N
fixed term 149.5 -> 126.2 MB (~23 MB back, per CEF PROFILE)
marginal 33.02 -> 34.22 MB/view (unchanged within noise)
r² 0.9999
The saving is per PROFILE, so it compounds with however many a host app
declares. Cost is one cold renderer launch for the NEXT browser created:
first paint 38 -> 69 ms (~+31 ms), 7/7 valid rounds. Nothing already
rendering is touched and no browser shares a process with another, so
there is no neighbour-jank trade.
Opt back in with FLUTTER_CEF_SPARE_RENDERER=1 where first-paint latency of
a newly created browser matters more than the memory. "0" and "" read as
off, so a wrapper can neutralize an inherited var without unsetting it.
Also here:
- `--disable-features` is now accumulated into one comma-joined value.
Chromium's CommandLine REPLACES on a repeated switch rather than
unioning, so adding a second one would have silently re-enabled the
MachPortRendezvous* bypass on ad-hoc builds — which blanks every tile.
- An unconditional one-line startup log naming the effective configuration,
so a measurement run can prove which binary it actually exercised. Two
sweeps in this investigation were invalidated by silently measuring the
wrong configuration; one stderr write per launch is the cheapest cure.
- A comment recording a NEGATIVE result so the next person doesn't repeat
it: `--renderer-process-limit` does not work here. Chromium treats it as
a soft limit and still gives every CEF windowless browser a dedicated
renderer — at N=16 under a cap of 8 there were 16 renderers. Its only
enforced effect is declining to warm the spare, a strictly weaker version
of this change.
The feature name was verified against this build's vendored binary rather
than from docs: "SpareRendererForSitePerProcess" appears verbatim in the
shipped framework beside the source path that gates on it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
What
Disables Chromium's spare renderer by default in
cef_host, recovering ~23 MB per CEF profile.Chromium keeps one pre-warmed spare renderer per profile so the next navigation can skip process startup. It is why renderer count measures N+1 at every N — including N=1, where a single-tile board pays for a second renderer it never uses.
Measured
Always-live bench (N mounted
CefWebViews, no canvas, no lazy-spawn gate),phys_footprintviatop -stats mem, shared profile, N ∈ {1,2,4,8,16}:The saving is per profile, so it compounds with however many a host app declares.
Cost: one cold renderer launch for the next browser created — first paint 38 → 69 ms (~+31 ms), 7/7 valid rounds. Nothing already rendering is touched, and no browser shares a process with another, so there is no neighbour-jank trade.
Opt back in with
FLUTTER_CEF_SPARE_RENDERER=1."0"/""read as off, so a wrapper can neutralize an inherited var without unsetting it.Also here
--disable-featuresis now accumulated into one comma-joined value. Chromium'sCommandLinereplaces on a repeated switch rather than unioning, so a second one would have silently re-enabled theMachPortRendezvous*bypass on ad-hoc builds — which blanks every tile.--renderer-process-limitdoes not work here. Chromium treats it as a soft limit and still gives every CEF windowless browser a dedicated renderer — at N=16 under a cap of 8 there were 16 renderers. Its only enforced effect is declining to warm the spare. Documented in a comment so the next person doesn't spend the day we spent.The feature name was verified against this build's vendored binary rather than from docs:
SpareRendererForSitePerProcessappears verbatim in the shipped framework besidespare_render_process_host_manager_impl.cc, the source that gates on it.Verified
Built from source (
build_cef_host.sh) against the patchedcampus-webauthn-h264framework.🤖 Generated with Claude Code