Chinese is the default; see RESULTS.md. English mirror here. Raw data:
out/results.csv(steady),out/stress_*.csv(stress curve). Every row carries full provenance (Migo version, device, WebView version, timestamp,fps_source). Migo build under test:release-tag:v0.9.6— the AAR you can download from the releases page (itslibmigo.sosha256 is byte-identical to the release artefact this page's site references), not a master commit. Raw dataout/matrix.csv(19 rows: 18 interleaved cells across three rounds + one canvasmark/migo make-up cell, each row carrying its thermal-gate verdict;matrix-summary.pyexcludes the single row that missed the gate). Reduction fixed byscripts/matrix-summary.py(median + retained range). Test build: Migo release (opt-z + LTO, the shipping config), host configured as a product would ship it (setDebugEnabled(false)). Everything on this page was re-measured on 2026-08-30 against the v0.9.6 release artefact. Two things to know:
- One capture-harness caliper. The capture script used to
adb installbefore every run, while the methodology says not to install between rounds — install resets the ART profile, so the launch right after runs pre-AOT code (the mechanism behind the 522 ms WebView first-frame in §5.2.1). It now installs only when the APK content actually changed, with one discarded run before the matrix. This fix landed for the 2026-08-25 (v0.9.4) pass and carries forward.endless-runnergame-ready reads 9% slower for Migo this pass (709 vs 648 ms). Not read as a version regression: the ranges overlap and this cell drifts +/-100 ms between sessions -- the previous session read it as a tie (663 vs 660) and v0.9.4 read it 10% slower (726 vs 660). Cross-session comparison is exactly what section 3 forbids; see section 1.- This page is now anchored to a commit, not a release tag. The
--migo-aar sha:<commit>path had never produced a usable row (the build log was written into the version field, so every record spanned multiple lines and the matrix kept only the last); fixed 2026-09-02, see MEASURING.md section 2c.
Same game, same device, same interaction. Migo native runtime (release) vs Android System WebView. Positioning: Migo = the source-available native WebView replacement.
- ✅ Memory: Migo uses 45–56% less (bunnymark 123 vs 233, endless-runner 214 vs 387, canvasmark 98 vs 221 MB). Fair accounting: WebView counts its separate chromium renderer process (else ~100MB is missed).
- ✅ CPU: Migo at a third to a half of WebView (2.2–3.0×) — bunnymark 2.8×, endless-runner 3.0×, canvasmark 2.2×.
- Startup: faster on 5 of 6 measurements. Faster: bunnymark first frame by 34%, bunnymark game-ready by 25%, endless-runner first frame by 12%, canvasmark first frame by 34%, canvasmark game-ready by 15%. Slower: endless-runner game-ready by 9%.
- = fps: a tie. Both sides hold a 60 fps median; 1% low is Migo 59, WebView 60.
- = Heavy load: a tie. Stressed to 220,000 sprites, the knee is at 40,000 on both sides and the curve is level or 1–2 fps in Migo's favour.
endless-runnergame-ready: Migo is 9% slower this run (Migo 709 ms, WebView 648 ms; ranges 604-712 and 643-664). Not read as a regression, for the same reason as v0.9.4: the ranges overlap, and this cell drifts ±100 ms between sessions on this device -- more than the gap itself. v0.9.4 lost this cell by 10% (726 vs 660) and was chased as a regression; that chase's conclusion is in section 3. The session before this one read 663 vs 660, a tie. Three readings spread over 663-726 against a WebView that sits at 648-660 say this cell cannot resolve a version difference, not that Migo got slower. Settling it needs a same-session A/B between builds (see CANVAS2D-SPRITES.md), not a subtraction between tables taken on different nights.
Note: high-end device only so far (Kirin 990). Mid- and low-end devices should widen the memory/startup gaps further — the key next test.
| Device tier \ Game | bunnymark (Pixi/WebGL) | endless-runner (Phaser/WebGL) | canvasmark (Canvas2D) |
|---|---|---|---|
| High-end · Huawei Mate30 Pro (Kirin 990 / 8G / Android 12) | ✅ done | ✅ done | ✅ done |
| Mid (~4G) | 🔜 | 🔜 | 🔜 |
| Low ⭐ (~2-3G) | 🔜 | 🔜 | 🔜 |
Each cell is the median of three interleaved rounds (see §5.2); within a round, WebView and Migo run the same game back to back.
| Metric | WebView | Migo | Delta |
|---|---|---|---|
| PSS peak | 233 MB | 123 MB | 47% less |
| CPU (multicore) | 119% | 42% | 2.8× less |
First frame (Displayed) |
348 ms | 229 ms | 34% faster |
Game-ready (Fully drawn) |
521 ms | 393 ms | 25% faster |
| fps median / 1% low | 60 / 60 | 60 / 59 | tie |
| Metric | WebView | Migo | Delta |
|---|---|---|---|
| PSS peak | 387 MB | 214 MB | 45% less |
| CPU (multicore) | 121% | 40% | 3.0× less |
First frame (Displayed) |
347 ms | 307 ms | 12% faster |
Game-ready (Fully drawn) |
648 ms | 709 ms | 9% slower |
| fps median / 1% low | 60 / 60 | 60 / 59 | tie |
| Metric | WebView | Migo | Delta |
|---|---|---|---|
| PSS (steady) | 221 MB | 98 MB | 56% less |
| CPU (multicore) | 164% | 75% | 2.2× less |
First frame (Displayed) |
346 ms | 227 ms | 34% faster |
Game-ready (Fully drawn) |
377 ms | 322 ms | 15% faster |
| fps median / 1% low | 60 / 60 | 60 / 59 | tie |
WebView renders portrait fit-scaled, Migo natively landscape (per game.json) — both render the whole game at the same pixel budget.
The Canvas2D path costs both sides more CPU than WebGL, so the CPU lead is smaller here. That is expected.
An in-game deterministic sprite ramp pushes the load to 220,000 sprites (far past any real mini-game). Each side runs twice, gated to the same starting temperature:
| Sprites | WebView fps (2 runs) | Migo fps (2 runs) | Migo/WebView |
|---|---|---|---|
| 40,000 | 60 / 59 | 60 / 60 | 1.01× |
| 70,000 | 42 / 42 | 45 / 45 | 1.07× |
| 100,000 | 31 / 31 | 32 / 32 | 1.03× |
| 140,000 | 22 / 22 | 23 / 23 | 1.05× |
| 180,000 | 16 / 16 | 18 / 18 | 1.12× |
| 220,000 | 13 / 13 | 13 / 13 | 1.00× |
The knee below 55 fps is at 40,000 sprites on both sides. This is a tie; through the middle of the ramp (70k–180k) Migo holds 1–2 fps more, and at 220k both hit the same wall. The curve shape matches the previous version.
One earlier claim is withdrawn. This page used to say Migo runs cooler at 220k (62.4 vs 66.1 °C) by spreading work across three CPU clusters while WebView pins its big core. Re-measuring on 2026-08-23 does not reproduce it: SoC peaks were WebView 62.5/64.6 °C against Migo 64.2/65.1 °C, with Migo marginally warmer, and the frequency samples show the governor taking the cluster to 2861 MHz in both runs. The original claim rests on a single measurement, so it is withdrawn.
Before this re-measurement the two sides were not measuring the same thing. All three are fixed; they did not all point the same way:
- Game-ready came from different events (favoured Migo). On the WebView side the game itself calls
AndroidBench.ready()from its first frame. On the Migo side the shell used the engine'sonGameReady, which fires when module evaluation finishes — before the first frame. The gap measures 32 ms. Both sides now fire from the same line of the same game: the migo shell injects anAndroidBenchof its own through a prelude script, routed back to the host over thegameLogchannel. - The shells were not structurally alike (worked against Migo). The migo shell was two activities and re-extracted the whole game bundle from assets on every launch; the webview shell is one activity reading straight from
file:///android_asset/. An activity transition and a full copy therefore sat inside every measured launch on one side only. Both are now one activity, and extraction happens once per game version — which is also what a real host does, at install or download time rather than at every launch. - Migo ran in a debug configuration (worked against Migo).
setDebugEnabled(true)registers an in-process console ring buffer that the WebView side has no equivalent of. It is now off, matching the shipping configuration.
setCodeSigningEnabled(false) stays: WebView verifies nothing per file beyond the APK signature, so per-file integrity checking on one side only would measure a feature the other does not have.
Device state drifts. The same unmodified WebView shell read anywhere from 380 to 524 ms across one night of testing — not thermal throttling (SoC 36.9 °C, battery 34 °C), but slow drift in device state. Any A/B taken across sessions is untrustworthy.
Every steady-state number on this page comes from interleaved measurement: one round is WebView and Migo back to back on the same game, three rounds, median per cell. The stress curve additionally uses a temperature gate (§4).
The bunnymark startup numbers published on this page before 2026-08-23 overstated WebView: first frame was given as 522 ms and game-ready as 650 ms, against a re-measured steady state of 354 / 529 ms. That is 170 / 120 ms, in Migo's favour.
The cause was that the WebView shell had just been adb installed when that round
was taken. A freshly installed APK has not been dex-optimised yet, and its first few
cold starts are measurably slower — and slower monotonically, not noisily: six
consecutive launches after a reinstall read 414 → 371 → 347 → …, settling at
338–360. Interleaving cancels device drift; it cannot cancel this, because this
applies to one side only — only the WebView shell had just been installed.
So the protocol gains a rule: both shells must already be installed and have run at least three times each before any number is recorded. Nor should an install sit between two measurements — writing a 361 MB APK perturbs the cold start right after it.
- Memory:
dumpsys meminfo; for WebView, main process +:sandboxed_process. - Startup: the system's own
amDisplayedandFully drawn, never app-log parsing. First frame (Displayed) means different things on the two sides — WebView paints a blank window first — but both numbers are listed. - fps: SurfaceFlinger
--latencywhere available; some devices (the EMUI build under test) return all zeros, in which case the game's own rAF telemetry is used (identical on both sides). Every row records itsfps_source. - CPU:
/proc/<pid>/statdeltas (WebView includes its renderer process), median over several windows. - Orientation: WebView locked portrait, Migo native per game.json — both render the whole game at the same pixel budget.
- Stability: screen forced on before capture (
svc power stayon).
A minimal host app, three integrations, single ABI (arm64-v8a), Mate30 Pro, median of five cold starts:
| Integration | APK added | Host cold start | Resident memory |
|---|---|---|---|
| No Migo (baseline) | — | 280 ms | 35.4 MB |
| Full AAR | +44.8 MB | 0 ms (277 ms) | +1.1 MB (36.5 MB) |
Full AAR, host calls MigoRuntime.getInstance() |
+44.8 MB | 0 ms (278 ms) | +1.1 MB |
-nojni AAR (engine delivered on demand) |
+0.23 MB | 0 ms (280 ms) | +1.1 MB |
- APK added is the on-disk size (
.sois stored uncompressed in an APK); the download delta is about +17 MB. - Cold start is unchanged and memory grows by roughly the SDK's dex alone, because the engine is not loaded until something actually needs it — even once the host holds a
MigoRuntime. -nojniis the same build withjni/**removed. The engine is delivered by the host the first time a user opens a mini-game, and verified against the manifest the AAR embeds.
- One high-end device so far (Huawei Mate30 Pro, Kirin 990). Mid- and low-end are the key next test.
- Energy uses CPU as a proxy (the test device's battery-stats interface is restricted); real power needs a device without that restriction or an external meter.
- Absolute numbers move with device state; what this page gives is a within-round, back-to-back comparison (§5.2).
- endless-runner game-ready is inside the noise and should not be cited as a lead (§1).
export PATH=$PATH:$ANDROID_HOME/platform-tools
# Migo release AAR (shipping config): scripts/build-aar.sh release arm64-v8a (in the migo repo)
# Interleaved: both sides back to back within a round, three rounds, median per cell (§5.2)
for round in 1 2 3; do for g in bunnymark canvasmark endless-runner; do
bash scripts/run.sh --runtime webview --game $g --device <SERIAL> --duration 12 --cold-runs 3
bash scripts/run.sh --runtime migo --game $g --device <SERIAL> --duration 12 --cold-runs 3 \
--migo-aar <path/to/migo-release.aar>
done; done
python3 scripts/compare.py --results out/results.csv --game bunnymark --vs-webview
# Temperature-controlled stress A/B (cool-down gate + three-cluster frequency sampling, 2 runs each; §4):
bash scripts/stress-ab.sh <SERIAL> <path/to/migo-release.aar>Baseline snapshot: baselines/mate30.csv.