Skip to content

perf(runtime): Map.clear early exit on empty maps; skip side-table probes for small numeric-keyed maps (ECS round 4) - #8938

Merged
proggeramlug merged 3 commits into
PerryTS:mainfrom
proggeramlug:perf/ecs-r4-map-clear
Aug 28, 2026
Merged

perf(runtime): Map.clear early exit on empty maps; skip side-table probes for small numeric-keyed maps (ECS round 4)#8938
proggeramlug merged 3 commits into
PerryTS:mainfrom
proggeramlug:perf/ecs-r4-map-clear

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

One runtime mechanism from the ECS round-4 chain, cut from current main. Measurement on the codehz/ecs "5k entities: 3 commands each + sync" row (paired alternating runs on the idle Mac mini) is being taken against current main and will be posted here; suites: runtime (2745) incl. map:: serial, on the isolated perrymaster gate.

  • Map.clear returns early on an empty map and skips the side-tables for small numeric-keyed maps. Every index js_map_clear resets mirrors the entries exactly (insert, delete, clear and the GC rewrites keep them in step), so an already-empty map has nothing to reset — the per-entity adds.clear(); removes.clear() of an ECS change set is that case half the time. And the string and pointer side-tables hold an entry only for a string or pointer key the map holds: for a map of at most 16 entries whose keys are all bits-stable numerics (is_safe_numeric_key), reading its keys is cheaper than the two thread-local resolutions plus two hash probes that find two empty tables — which the post-4h profile showed was the whole cost of js_map_clear (both hot offsets on its two TLS probes, ~10k clears a frame on the grouping maps, 1.1% of the frame). Larger maps and any string/pointer key reset the side-tables unconditionally as before. clear_resets_every_index_whatever_the_key_kinds pins the numeric-only, empty, string-keyed and past-the-bound shapes (a stale content-hashed index entry would otherwise misroute a re-added key).

https://claude.ai/code/session_01FUvFrRNZyc5qknBiJbYbby

Summary by CodeRabbit

  • Performance

    • Improved Map.clear() performance, especially for frequently cleared small maps.
    • Empty maps now clear immediately.
    • Small maps with numeric keys avoid unnecessary internal cleanup work while preserving correct behavior.
  • Bug Fixes

    • Ensured all map indexes are reset correctly across numeric-keyed, string-keyed, and larger maps.

…side-tables for small numeric-keyed maps

Every index js_map_clear resets mirrors the entries exactly, so an
already-empty map has nothing to reset — the per-entity
`adds.clear(); removes.clear()` of an ECS change set is that case half
the time. And the string and pointer side-tables hold an entry only for a
string or pointer key the map holds: for a small map whose keys are all
bits-stable numerics, reading its keys is cheaper than the two thread-local
resolutions plus two hash probes that find two empty tables, which was the
whole cost of the ten thousand clears a frame on the grouping maps.

Claude-Session: https://claude.ai/code/session_01FUvFrRNZyc5qknBiJbYbby
@coderabbitai

coderabbitai Bot commented Aug 28, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d174540d-2ee4-49ee-be7b-f7673d1a270d

📥 Commits

Reviewing files that changed from the base of the PR and between 924dd16 and 2b760ec.

📒 Files selected for processing (2)
  • changelog.d/8938-map-clear-fast-paths.md
  • crates/perry-runtime/src/map.rs

📝 Walkthrough

Walkthrough

Map.clear() now returns immediately for empty maps and avoids string/pointer side-table clears for small maps with safe numeric keys. Tests cover numeric, string-keyed, empty, and large maps.

Changes

Map.clear optimization

Layer / File(s) Summary
Implement conditional Map.clear paths
crates/perry-runtime/src/map.rs, changelog.d/8938-map-clear-fast-paths.md
js_map_clear adds an empty-map fast path and skips side-table clearing for small maps whose keys are safe numerics. The changelog documents the optimization.
Validate clear behavior
crates/perry-runtime/src/map.rs
Tests cover numeric-only maps, string-keyed maps, empty maps, and maps above the scan limit.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: thehypnoo

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@proggeramlug

Copy link
Copy Markdown
Contributor Author

Merged. Rebased onto current main; it conflicted with #8934 in map.rs because both add a test at the same spot and the two bodies share one closing brace. Kept both tests, each with its own brace and #[test].

Validation: runtime 2770/0 (RUST_TEST_THREADS=1), scripts/run_lint_gates.sh 57 of 58 including the compile tier green — the exception is the pre-existing ${{ }} substitution artifact (#8929).

@proggeramlug
proggeramlug merged commit a1e44c5 into PerryTS:main Aug 28, 2026
15 of 16 checks passed
@proggeramlug

Copy link
Copy Markdown
Contributor Author

Paired measurement (idle Mac mini, 9 alternating pairs, codehz/ecs "5k entities: 3 commands each + sync"): control = main 49279e99e, candidate = this branch on that base: 3.955 → 3.943 ms/op, +0.36%, 9/9 (r4j-screen.json, process oracles 18/18). Small, unanimous.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant