Reproducible Migo vs Android System WebView benchmarks — the same game, same device, same interaction script, on both runtimes. The evidence behind Migo's "open-source native runtime that replaces the WebView" positioning.
Mate30 Pro · release build · 3 games (Pixi/WebGL, Phaser/WebGL, Canvas2D) · every bar traces to a pinned Migo version. Full per-metric tables → RESULTS.md (中文) / RESULTS.en.md.
Status — Migo is pre-1.0, actively shipping. Every release since v0.9.0 has published a runnable, attested AAR — reproduce every number yourself with
--migo-aar release-tag:v0.9.3(or any tag from minigame-labs/migo/releases). This repo is the public, auditable evidence trail behind those numbers.
This repo is both a showcase (adopters/skeptics can re-run it) and a regression harness (every Migo optimization/fix re-runs the same comparison against a baseline). A credible, reproducible benchmark is the marketing artifact — credibility is the sell.
RESULTS.md(中文,默认) · RESULTS.en.md (English) — device × game matrix + per-metric tables (memory, startup, fps + stress curve, CPU, energy). TL;DR on Mate30 Pro, consistent across all three games (bunnymark Pixi, endless-runner Phaser, canvasmark Canvas2D), all verified rendering full-screen: memory Migo 47–61% less · CPU 2.3–3.0× less · first frame 18–38% faster and game-ready 6–25% faster on all three · fps a tie (60 median both sides; 1% low 59 vs 60). The endless-runner game-ready lead is the thin one — read §1 before quoting it. ✅ Heavy-load scaling holds up — stress-tested to 220k sprites (far past any real mini-game's normal load): the knee is at 40,000 sprites on both sides and the curve is level or 1 fps in Migo's favour the whole way. An earlier version of this line also claimed Migo ran cooler; that did not reproduce on 2026-08-23 and has been withdrawn — see RESULTS §4.
JITLESS.md — what a V8 that cannot generate code costs, measured on
hardware. HarmonyOS NEXT forbids third-party JIT, so this is the number that decides whether
Migo's performance there may be spoken about at all. Two findings: --jitless does not slow
WebAssembly down, it removes it (typeof WebAssembly === "undefined"); and while the three
steady-state games all tie at a vsync-capped 60 fps, the stress ramp shows the cap was hiding
everything — the 60 fps knee moves from 40,000 sprites to 2,000, and at equal load jitless
returns 2.4× to 16× fewer frames.
CODEGEN.md — whether raising the codegen level on the five hot crates buys
anything. It does not: twelve cells, not one moved above §4b's noise floor, for +1.29 MiB of
.so — enough to put .text 44 KB over its own size budget. Two cells did cross the floor, in
opposite directions, and both dissolve into a single round when you look at the paired deltas;
that is the clearest demonstration this repo has of why n=3 ranges are not evidence.
MEASURING.md — how to take a number here without fooling yourself. Thirteen traps, each one of which has already cost this repo a wrong published number or a wasted day. Read it before you re-measure anything.
- Headline — consistency & auditability + memory. Migo bundles ONE runtime → identical behaviour everywhere; WebView drifts across OEM/OS/versions. Migo is open, pinnable, fixable; WebView is a black box that updates out-of-band. Memory footprint is measurably lower.
- Supporting — efficiency. cold-start (game-ready), PSS memory, CPU, energy, size.
- Report-honestly — throughput. fps usually ties; never led with. fps is Migo's control point (tunable, e.g. cap at 30 for battery), paired with energy.
Every headline metric is read from Android, not the app's self-report. fps uses a layered
source recorded per row as fps_source:
dumpsys SurfaceFlinger --latencypresent-timestamps (true displayed rate; works for Migo's native SurfaceView and WebView) — auto-detected layer.- Fallback on restricted OEMs (EMUI/Huawei return all-zeros): the game's own telemetry for BOTH runtimes (identical instrumentation both sides = fair). Never mix a system source for one side with an app source for the other.
cold-start = reportFullyDrawn() + am start -W. memory = dumpsys meminfo.
games/ game payloads (bunnymark Pixi/WebGL, endless-runner Phaser/WebGL, canvasmark Canvas2D)
shells/ webview-shell + migo-shell (symmetric minimal apps, each loads one game directly)
scripts/ lib.sh, capture-*.sh, run.sh, parse.py, compare.py, resolve-migo-aar.sh
baselines/ pinned reference result rows (regression gate compares new runs against these)
out/ results.csv + raw logs (gitignored except results.csv)
tests/ parse.py + compare.py fixture tests
.github/ host CI (pytest, script lint, webview-shell build, compare self-test)
- WebView baseline is a modern shell (compileSdk 34) — never an old template.
- Lead with consistency/memory; report fps honestly (it ties).
- Every result row carries provenance: migo version, device, WebView version, harness
version, timestamp,
fps_source. Results are tied to an exact Migo version (auditability).
export PATH=$PATH:$ANDROID_HOME/platform-tools # adb
python3 scripts/parse.py --header-only > out/results.csv
# WebView baseline:
bash scripts/run.sh --runtime webview --game bunnymark --device <SERIAL> --duration 60 --cold-runs 3
# Migo (pin a version: local dev AAR, a release tag, or a git sha):
bash scripts/run.sh --runtime migo --game bunnymark --device <SERIAL> --duration 60 --cold-runs 3 \
--migo-aar local:$HOME/wkspace/migo/platforms/android/dist/migo-release.aar
column -t -s, out/results.csvThe authoritative numbers, the device × game matrix, and every per-metric table live in RESULTS.md (中文) / RESULTS.en.md — not duplicated here.
bash scripts/run.sh --runtime migo --game bunnymark --device <SERIAL> --scenario stress --duration 55 --migo-aar local:...
bash scripts/run.sh --runtime webview --game bunnymark --device <SERIAL> --scenario stress --duration 55
# -> out/stress_{migo,webview}.csv (runtime,sprites,fps_median)A deterministic in-game sprite ramp (2k→220k, 5 s per stage — Pixi-ticker based, identical
both sides; scripts/make-stress-game.sh generates it from the normal bundle) drives the load
while the harness records bunnies=N fps=M. fps is plotted against N. The two curves track
each other across the whole ramp (Migo at parity, edging ahead at high load) — see RESULTS §4.
scripts/stress-ab.sh runs this cold-gated with per-cluster frequency logging.
The whole point of the framework: any future Migo fix/optimization re-runs the same capture and
is diffed against a pinned baseline. scripts/compare.py turns two results.csv into a verdict.
# 1) Showcase table — Migo vs WebView for a game (from one results.csv):
python3 scripts/compare.py --results out/results.csv --game bunnymark --vs-webview
# 2) Regression gate — a NEW Migo build vs the committed baseline (same game).
# Exits non-zero if any metric regressed past --threshold (default 5%) -> gate a PR.
python3 scripts/compare.py --results out/results.csv --baseline baselines/mate30.csv --game bunnymarkMetrics carry a direction (memory/CPU/startup lower-better, fps higher-better); a change within
the threshold is treated as single-run noise. Baselines are committed under baselines/ and
stamped with the Migo version they were captured against. .github/workflows/ci.yml runs the
host-side checks (pytest, script lint, the WebView shell build, and a compare self-test) on every
push — real-device capture stays local (a hosted runner has no phone).
The harness takes --migo-aar <release-tag | local:PATH | sha> so a WIP fix benches against a
local dev AAR and published numbers pin a release tag. Every result stamps the resolved version.
- Commercial licensing: licensing@minigame-labs.com
- Security reports: see SECURITY.md