gorge's engine speed¶
Engine throughput matters twice over: once for hosting games, and again because search bots clone and step the engine thousands of times per decision. This page is for contributors changing engine code: what gorge's own benchmark measures, how to run it, the current numbers and where the time goes. For gorge against other engines see Performance; for how those runs are made see Methodology.
What is measured¶
| Tool | Measures | Output |
|---|---|---|
cmd/enginebench (one row) |
Fixed workloads: random and bot play (turns, games and decisions per CPU-second); clone and step (µs per op with and without GC, bytes and mallocs per op, retained bytes per clone); az and sampler search rows (simulations per second, ms per root); live heap after the corpus loads |
one JSON line per run, with the commit, Go version, CPU model, GOGC and core pin |
make enginebench-pair BASE= CAND= |
A paired, interleaved ABBA comparison of two revisions, each built from git archive |
JSONL plus a text table: per-row median throughput and the median per-repetition candidate/base ratio |
make enginebench-profile [REV=] |
CPU and allocation pprof per row, and the GC share | pprof files and printed tops |
make enginebench-verify [REV=] |
A memo-verifying build; fails on errors, stalls, panics or stale memos | pass/fail |
scripts/xengine-bench.sh |
The cross-engine rows: gorge's random, random -autopay, bot and bot -autopay rows on both pairs, pinned, three reps, next to the other engines |
runs.jsonl and a summary table |
make sim |
20 verified four-seat games | replay OK count |
make gc-gate |
GC CPU share of one package's tests (default ./server/host, budget 0.30) |
ratio |
The workloads use four 17lands Foundations (FDN) draft decks (CC BY 4.0) and
an mtg-kernel Burn deck (MIT), in
cmd/enginebench/testdata/decks.
Pair A is UG vs WG, pair B is WR vs UR. Seats alternate every game; game g
uses seed base + g, so a row is reproducible.
The rows, and what each exercises¶
flowchart TB
subgraph play["whole games (games/s, turns/s)"]
R["random: uniform over every option,<br/>each mana tap asked"]
RA["random -autopay: uniform over<br/>auto-pay candidates (payment plans)"]
B["bot: heuristic.NewBot,<br/>manual taps"]
BA["bot -autopay: EnableAutoPayMana"]
end
subgraph ops["one operation (µs/op, bytes/op)"]
C["clone: Engine.Clone of a mid-game root"]
S["step: clone + submit one candidate"]
end
subgraph search["search (sims/s, ms per root)"]
AZ["az: azmcts, honest re-deal<br/>(build tag enginebench_az)"]
SA["sampler: the older rollout sampler"]
end
R --> E[(rules engine)]
RA --> E
B --> E
BA --> E
C --> E
S --> E
AZ --> S
SA --> S
How to run¶
go run ./cmd/enginebench -row random -pair A -secs 20 # one row, one JSON line
make enginebench-pair BASE=main CAND=. # paired A/B; BUDGET=60s for the whole run
make enginebench-pair BASE=main CAND=. PIN=24 REPS=3 # pinned to one core, GOMAXPROCS=1
make enginebench-pair BASE=main CAND=. BUILD_ARGS="-tags enginebench_az" ROWS="-row az -sims 100"
make enginebench-profile REV=.
make enginebench-verify REV=.
Knobs: BUDGET (wall seconds for the whole measured phase, default 60),
ROWS, REPS, SECS, PIN, GOGC, BENCH_DIR, HEAVY_LOCK (see
scripts/enginebench-lib.sh).
Noise
The reference machine is a shared 32-thread Ryzen 9 9950X (Go 1.26.3) whose load varies from about 5 to 80. Pinned single-core rows with nothing else on the core repeat to within 1-5% (the spreads below); unpinned rows on a loaded machine move ±15-30%. Only paired, interleaved A/B ratios are trustworthy for judging a change; treat absolute numbers as indicative of this machine.
Current numbers¶
Measured 2026-10-20 at ad3777023, Go 1.26.3, GOMAXPROCS=1, each run
pinned to one core (24, 26 or 28; three reps of a row at once), 30 CPU-seconds
per run, load average 5-7, by scripts/xengine-bench.sh gorge (the play rows)
and single enginebench runs (the rest). Median of three reps, min-max in
brackets.
| Row | Pair A | Pair B |
|---|---|---|
random -autopay, games per core-second |
92.3 (91.9-93.9) | 86.8 (85.4-86.8) |
random -autopay, µs per turn / per decision |
471 / 19.7 | 445 / 18.9 |
random (manual taps), games per core-second |
103 (102-103) | 108 (106-108) |
random, µs per turn / per decision |
232 / 8.5 | 233 / 8.5 |
bot -autopay, games per core-second |
103 (102-103) | 111 (110-112) |
bot -autopay, µs per turn / per decision |
540 / 21.5 | 509 / 21.0 |
bot (manual taps), games per core-second |
134 (132-136) | 144 (144-145) |
bot, µs per turn / per decision |
412 / 14.7 | 388 / 14.0 |
Turns per game: random -autopay / random / bots |
22.9 / 41.9 / 18 | 26.0 / 39.8 / 18 |
| Peak RSS of a play run (corpus loaded) | 346-355 MB | 347-348 MB |
| Row (pair A, one run each, same date and build) | Value |
|---|---|
clone, mid-game roots (turns 5-8) |
13.6 µs per clone without GC, 28.3 µs with GC; 181 KB and 50 mallocs per clone |
step (clone + submit one candidate) |
39.3 µs without GC, 59.0 µs with GC; 357 KB and 132 mallocs per step |
az -sims 100 (azmcts, honest re-deal, heuristic leaf) |
1,129 simulations per core-second, 88.6 ms per searched root (332 roots) |
sampler (the older rollout sampler) |
109 rollouts per core-second, 242 ms per root |
| Live heap after the corpus loads | 36 MB |
Running the three reps of a row at once costs little: a random -autopay
pair-A run alone on core 24 the same afternoon gave 95.6 games per
core-second, 3.5% above the concurrent median. The 2026-10-07 single-core row
set (random A 75.7 games/s, bot A 121 games/s, clone 16.5 µs, MCTS-100 595
sims per CPU-second) ran with GOMAXPROCS=1 at load 34-80, so the change
since then mixes the machine's load with the code's; use make enginebench-pair to compare
revisions.
Where the time goes¶
CPU profiles of one 20-second pair-A run each (enginebench -cpuprofile,
same build, core 24/26), flat time grouped by package (packages under 1%
omitted; engine/rules includes engine/rules/pay):
| Package | random -autopay |
bot -autopay |
|---|---|---|
engine/rules (legal-action walk, payment planning, resolution, triggers) |
64.1% | 56.2% |
| Go runtime (GC, allocation, maps) | 12.0% | 11.8% |
engine/state |
7.2% | 7.1% |
bots/botpolicy + bots/heuristic (the bot) |
- | 5.8% + under 1% flat |
engine/cards |
5.0% | 4.4% |
engine/effects |
4.4% | 4.0% |
engine/events |
1.3% | under 1% |
Go standard library (strings and others) |
about 2% | about 4% |
By call tree (cumulative), in the random auto-pay run: Engine.Submit 72%
and Engine.Advance 39% (they nest); offering the auto-pay priority options
(paymentActionsForPriority) 25%; the legal-action walk over the hand
(legalWalk.handWalk) 24% and over the battlefield 10%; emitting events
through events.Apply (Engine.emit) 17%. In the bot run the bot's own
work is about 15% cumulative: building its board (botpolicy.BoardFromGameInto,
9.9%) and deciding (heuristic.(*Bot).DecideBoard, 5.1%). So in both rows
the engine, not the policy, is most of the cost: listing what a player may
do, with payments planned, is the hot path.
To reproduce: go run ./cmd/enginebench -row random -autopay -pair A -secs 20 -cpuprofile ra.cpu,
then go tool pprof -top -cum ra.cpu, or make enginebench-profile.
Trajectory¶
These are historical results, kept as recorded; rows before 2026-10-06 are not comparable with later ones (see the baseline break).
| Date | Commit(s) | Workload | Result |
|---|---|---|---|
| 2026-09-21 | 4744d8a7, 1c485b7d |
PIMC search teacher | 496 → 171 ms per decision at 4 workers |
| 2026-09-27 | a60dfcd9e |
bot sweep | −16.6% sweep CPU; rules tests 50 s → 15 s |
| 2026-09-27 | bc14fd36a, 43a60ba67, … |
bot sweep | manual taps −24…32%, auto-pay −22…47% |
| 2026-09-30 | a4af596 vs e0496f062 |
enginebench, pinned |
main 7–25% slower than the published baseline; live heap 143 MB; top hotspot an index rebuild at 12.4% |
| 2026-09-30 → 10-01 | 308f7e0b9 → 9226f882e |
SpellBench-style MCTS games | MCTS 3.9× less CPU / 4.9× less wall; other search seats 5–8× |
| 2026-10-05 | 08f248918 |
rules test gate |
−44% CPU, −47% allocation |
| 2026-10-06 | a6107d0df, d55a968a8, a3b5123a3, b75197f98, 3f88c20d0 |
corpus image, event log, decision arena | live heap 143 → ~41 MB. Baseline break: random/bot rows now recycle games through a spare pool |
| 2026-10-07 | ~49c8224ce |
enginebench, GOMAXPROCS=1 |
vs 2026-09-30: random A 4.3×, Burn 2.9×, bot A 3.5×, auto-pay 2.3×, MCTS-100 1.8×, MCTS-1000 2.7× (crosses the baseline break; directional only) |
| 2026-10-07 | heap-attack branch | self-play | +10.8% combined; allocated objects −30% |
| 2026-10-09/10 | quiet-machine series | MCTS-100 | +10.4% median |
| 2026-10-20 | ad3777023 |
enginebench, pinned, 3 reps (this page) |
see Current numbers; live heap 36 MB |
The engine-speed method and the first pinned comparison are written up in
the archived 2026-09-30-gorge-engine-speed.md
(archive index).
Tracking over time¶
A nightly series anchored to a fixed revision is planned but not recorded yet; Tracking over time says which parts exist.