874b6f20c2
Основа — Horizon (MIT). История апстрима не переносится: этот репозиторий ведёт свою.
82 lines
4.3 KiB
Markdown
82 lines
4.3 KiB
Markdown
# Nether-portal section-scan benchmark
|
|
|
|
Run with the pinned prepared client resources:
|
|
|
|
```sh
|
|
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.
|