Repository navigation
Conversation
ClockCache is now an open-addressing table of immutable entries, published atomically and read with acquire loads, so hits take no lock and write no shared cache line (hits and misses are counted per thread). Misses and all other mutations still take the lock. Shared hits stay at ~25 ns at 8 threads (was ~500 ns) and are 2x faster single-threaded. Task-local caches keep the previous locked implementation, now the internal LocalClockCache, to avoid the per-thread counters in every task's cache. Also: iteration has unknown length (collect could throw under threads), and get! forwards to _get! with a specialized default (no allocation on 1.10). Adds concurrency stress tests, benchmark/contention.jl and research/lockfree-clock.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Member
Author
|
Closing for now. The branch Why: the design works and passes every check, but it adds about 200 lines with memory-ordering invariants, and one of them relies on undocumented codegen behaviour. The evidence that contention matters in practice is thin:
State:
Already split out: the When to revisit: if the re-run of the TensorKit/SUNRepresentations integration with a threaded workload shows contention on |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
With this change,
ClockCachehits take no lock, so a cache shared between threads scales on hits. Misses, insertions, evictions and every other change still take the lock.ClockCachekeeps its name and stays the default container.Design
@atomic ref::Bool, and is only written when it is clear. Hot entries cause no writes at all.clock.jl. Rationale and alternatives are inresearch/lockfree-clock.md:Dictwithout the lock is memory-unsafe;LocalClockCache, so they don't carry the per-thread counters (64 B per thread per cache).Also fixed
collect(c)could throw under threads, for every container, because it sized its result from one snapshot and filled it from another.get!hashes the key once and specializes ondefault. Without that, Julia 1.10 allocated on hits.Benchmarks (Julia 1.13, ns per lookup per task)
@cachedhits,Intkeys@cachedhits,Intkeys@cachedhits, 4-tuple keysShared hits still allocate 0 bytes. The benchmarks are in
benchmark/contention.jl.Tests
-t 8on 1.10, 1.11, 1.12 and 1.13. That includes the new stress test, in which many tasks run every operation concurrently and every returned value is checked against its key.Known risk
The release half of publishing a slot relies on
setindex!compiling to a release store. That is codegen behaviour, checked on 1.10–1.13, not documented API.AtomicMemorywould make it explicit once 1.10 is dropped.🤖 Generated with Claude Code