Skip to content

Read registered component indices lock-free - #189

Merged
ctreffs merged 6 commits into
mainfrom
lockfree-component-index
Oct 31, 2026
Merged

ctreffs merged 6 commits into
mainfrom
lockfree-component-index

Conversation

@ctreffs

@ctreffs ctreffs commented Oct 30, 2026

Copy link
Copy Markdown
Member

Description

Types that conform to Component without the @Component macro resolve componentIndex through Nexus.ComponentIndexRegistry on every typed access. Before this change, every such access took a global pthread mutex and did a SipHash dictionary probe. Found while analysing the entity.lookup.none regression in the benchmark comparison (#187).

Lookups of already registered types are now lock-free. Readers run in parallel and never block, and only the first registration of a type takes the lock.

Detailed Design

  • New in-package C target CFirebladeAtomics, which adds no new dependency. It provides two static inline helpers, an acquire load and a release store on a plain uintptr_t word, built on __atomic_load_n and __atomic_store_n. The package declares no minimum platform, so Synchronization.Atomic is not an option.
  • An open-addressing table (power-of-two capacity, load factor at most 0.5, multiply-shift hash of the ObjectIdentifier bits) is published through an atomic pointer.
    • Read: an acquire load of the table pointer, a probe that acquire-loads each key, and the index read on a match.
    • Miss: falls back to the locked path, which re-checks and then inserts. The index word is written before the key is release-stored.
    • Growth: the table doubles and is republished. Superseded tables are kept and never freed, so a late reader never touches freed memory. Total memory is about 2× the final table, roughly 16 KiB for 256 types.
  • No public API changes. Every guarantee is unchanged: the same index per type for the life of the process, dense assignment in first-use order, the indexLimit precondition, count, indices assigned by the macro, and the WASI no-op lock.

Documentation

The design and memory bounds are in the symbol docs of ComponentIndexRegistry in ComponentIndex.swift.

Testing

  • Deterministic table tests: forced collisions with wraparound, growth while a reader still holds the old table, and a stale miss falling back to the locked path.
  • A real-thread contention test: 8 writers and 8 readers released by a barrier.
  • A concurrent registration test over many generic types.
  • make test and make build-release pass. swift test --sanitize=thread --filter ComponentIndex reports no data races.
  • An independent review found no blockers.
  • Not run locally: Windows, WASI and 32-bit builds. CI covers them.

Performance

Micro-benchmark, release build, best of 7, 20M calls, ns per call:

Call main this PR
missed get, plain Component 32.4 13.1
hit get, plain Component 32.3 13.0
missed get, @Component 10.3 10.3

Source Impact

None, apart from the new internal C module.

Checklist

  • I've read the Contribution Guidelines
  • I've followed the coding style of the rest of the project.
  • I've run make pre-commit and verified all checks pass. make test passes. make lint has no findings in the changed code but fails on indentation errors that already exist on main.
  • I've added tests covering all new code paths my change adds to the project (to the extent possible).
  • I've added benchmarks covering new functionality (if appropriate). The micro-benchmark above was not committed; the replicated comparison is tracked in Benchmarking #187.
  • I've verified that my change does not break any existing tests or introduce unexpected benchmark regressions.
  • I've updated the documentation (if appropriate).

https://claude.ai/code/session_01QVgj7qq418b9eVorviMYDS

@ctreffs
ctreffs marked this pull request as ready for review October 31, 2026 00:14
@ctreffs
ctreffs merged commit 0d68837 into main Oct 31, 2026
9 checks passed
@ctreffs
ctreffs deleted the lockfree-component-index branch October 31, 2026 00:15
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