Основа — Horizon (MIT). История апстрима не переносится: этот репозиторий ведёт свою.
4.3 KiB
Nether-portal section-scan benchmark
Run with the pinned prepared client resources:
CARGO_BUILD_JOBS=6 cargo run --profile fast -p horizon-client --example portal_scan_bench -- \
.local/horizon-client/26.3
The example keeps the original Game::may_hold_portal alongside the resident
cache (portal_sections). Both run in the same optimized binary, alternating
measurement order. Each workload has one discarded warmup pair and seven pairs
of 300 ticks. It reports resource loading and fixture construction separately.
This measures the section scan only, not the whole entity_portals span, the
sweep that follows a positive answer, or the rest of the tick.
The fixture is a 17x17 chunk world (289 chunks), each with 24 sections, and 500
entity reaches spread over it (an item or mob box 0.25 wide and the small move
that produces the swept box). A reach touches 1-4 sections. Four palettes:
sparse (one non-portal state), dense (128 distinct), global (300 distinct, a
global palette whose maybe_has walks the whole state range) and dense_portal
(the dense palette with a portal in every fifth section). These are controlled
fixtures, not a claim about typical generated terrain.
Steady includes the initial cache fill amortized over 300 ticks. Cold recreates
the cache each tick, including allocation and destruction; reload clears it each
tick while retaining capacity. Edit workloads alternate a block value in three
or all 289 chunks before every scan. Timing includes the Arc::make_mut move of
the 176-byte chunk wrapper caused by weak cache handles; the baseline holds no
such handles. Both paths keep the original outer reach list.
The scan is parity-checked in the same run: the cached and per-tick answers must agree for every reach over every sample, or the example panics.
Measured on an AMD Ryzen 5 5600X (12 logical CPUs), Linux x86-64, rustc 1.99.0
(b940084d7), using fast (release optimization, LTO off, 16 codegen units).
Resource loading took 705 ms; fixture construction took 66 ms sparse, 360 ms
dense, 392 ms global and 385 ms dense_portal, shared by both implementations.
Median microseconds per tick (seven paired samples):
| Palette | Workload | Original | Cached | Speedup |
|---|---|---|---|---|
| sparse | steady | 40.058 | 16.836 | 2.38x |
| sparse | cold | 40.203 | 48.619 | 0.83x |
| sparse | reload | 40.828 | 44.552 | 0.92x |
| sparse | edit_1pct | 41.282 | 52.200 | 0.79x |
| sparse | edit_all | 50.119 | 4192.396 | 0.01x |
| dense | steady | 244.610 | 21.869 | 11.19x |
| dense | cold | 292.533 | 1505.691 | 0.19x |
| dense | reload | 250.397 | 1301.608 | 0.19x |
| dense | edit_1pct | 256.522 | 69.132 | 3.71x |
| dense | edit_all | 263.829 | 5904.581 | 0.04x |
| global | steady | 6179.070 | 360.082 | 17.16x |
| global | edit_1pct | 6251.530 | 1565.249 | 3.99x |
| dense_portal | steady | 256.854 | 21.078 | 12.19x |
| dense_portal | cold | 240.498 | 1526.251 | 0.16x |
| dense_portal | reload | 347.879 | 2037.123 | 0.17x |
| dense_portal | edit_1pct | 368.684 | 118.108 | 3.12x |
| dense_portal | edit_all | 282.876 | 7194.112 | 0.04x |
The global palette's maybe_has walks the whole ID range, so rescanning every
chunk each tick (cold, reload, edit_all) is intractable; those rows are not
measured for it.
Keep: the steady scan improves for every palette (2.4x to 17x), which is the case that repeats each tick in real play. Under constant edits the cache also improves the dense and global palettes (edit_1pct 3-4x) because the original rescans through the per-tick map. Rescanning every chunk every tick (cold, reload, edit_all) is slower than the original because a miss scans all 24 sections of a chunk, where the original only touched the sections a reach overlaps. No whole-game speedup is inferred from this microbenchmark; real terrain's palette sizes and block-write frequency determine the gain. Lighting and other chunk changes conservatively invalidate a chunk's mask.
The cache adds a weak handle and mask per visited resident chunk, retains no
unloaded block data, and is pruned when a chunk unloads (sync_tick_chunks) and
cleared on resource reload, like random_tick_sections. Editing a cached chunk
can move its 176-byte wrapper (plus Arc bookkeeping) on the first edit after it
is cached; it does not force a deep copy of the block data. The original outer
reach-list allocation remains.