Files
RustNotMC/docs/performance/paletted-container-access-results.md
T
SashegDev 874b6f20c2 Клиент: начальный импорт кодовой базы
Основа — Horizon (MIT). История апстрима не переносится: этот репозиторий ведёт свою.
2026-10-29 22:04:28 +03:00

3.8 KiB

Paletted container access

The unpack/get cell reader and BlockSection::get/set are hot in the chunk workers: light and mesh workers unpack a section's 4,096 cells before propagating or meshing, and world-generation workers write one block at a time through BlockSection::set.

The write path resolved the old cell and probed the palette three times; the bulk readers dispatched the palette per cell; and the storage layer revalidated indices and values a section had already checked. All three are addressed.

Changes

BlockSection::set (crates/horizon-world/src/chunk.rs) previously resolved the old cell through get and then probed the section's palette map three times for one non-trivial write: once to key the old state by id (local_ids[&old]), once to test the incoming state (contains_key), and once to find it (get). The slot at the written index already is the old state's id, so the old state is now read straight from storage, and the incoming state is resolved with a single map probe. The palette-reuse, free-slot and growth paths are unchanged, so the encoded palette, counts, free list and NBT output stay identical.

BlockSection::get and set validate the section index and the state id once, then read and write through PackedStorage::read/write (crates/horizon-core/src/storage.rs). These skip the storage-level bounds and value checks and the Result that PackedStorage::get/set keep for external callers: a section index is below 4,096 and a palette id always fits the width bits_for_palette chose. Both new methods debug_assert their contract and are used for the hot read, the hot write and the palette-growth re-encode.

PalettedContainer::unpack and unpack_u16 (crates/horizon-core/src/paletted.rs) matched on the palette variant inside the per-cell loop. Single, List and Global are now dispatched once before the loop, leaving each arm a branch-free indexed loop.

No allocation, layout or wire format changes.

Reproduce

cargo test -p horizon-world --lib --profile fast benchmark_section_get_set -- --ignored --nocapture
cargo test -p horizon-core  --lib --profile fast benchmark_unpack            -- --ignored --nocapture

The ignored tests compare the new code against copies of the pre-optimization implementations, kept in the test modules (baseline_get/baseline_set, baseline_unpack). They check every resulting palette, count, free list, cell and NBT before timing. Rounds alternate baseline/optimized order with black_box and report the median and best nanosecond- or microsecond-per-operation; the best round is the estimate of uninterrupted CPU time on a contended machine.

benchmark_section_get_set uses a 32-state registry and 50,000 random writes and reads per round over a section whose palette grows through the 4- and 5-bit widths (24 distinct states); 51 rounds. benchmark_unpack fills a 4,096-entry list palette (12 states) and the global palette, 2,000 calls per round, 25 rounds.

AMD Ryzen 5 5600X, Linux x86_64, rustc 1.99.0, optimized fast profile, nonincremental, no LTO:

Operation Original main New Speedup
BlockSection::set 13.15 ns/write 9.12 ns/write 1.44x
BlockSection::get 2.33 ns/read 1.45 ns/read 1.61x
unpack, list palette 1.910 us/call 1.834 us/call 1.04x
unpack, global palette 2.270 us/call 2.179 us/call 1.04x

The distinct-state differential test chunk::tests::set_matches_baseline_under_random_edits drives the optimized set and get and the originals over the same 50,000-edit streams for 1, 2, 16, 17, 24 and 64 distinct states, including periodic whole-section collapses that clear the storage and reclaim palette slots, and asserts equal results, bits, palette, counts, free list, local_ids and NBT throughout.