Skip to content

ThreadSafeBitSet.set: skip the CAS when the bit is already set (performance fix) - #871

Merged
Sunjeet merged 1 commit into
Netflix:masterfrom
arpitmalhotra:fix/threadsafebitset-skip-cas-when-set
Oct 14, 2026
Merged

Sunjeet merged 1 commit into
Netflix:masterfrom
arpitmalhotra:fix/threadsafebitset-skip-cas-when-set

Conversation

@arpitmalhotra

Copy link
Copy Markdown

HollowTypeWriteState.add() calls currentCyclePopulated.set(ordinal) for every record written, including the common case where the same record is written many times in a cycle (e.g. shared strings, tags or category ids referenced from many collections). set() always performed a compareAndSet, even when the bit was already set, which forces the cache line holding that word into the exclusive state on every call.

For small, frequently referenced types the populated-ordinals bitset is only a few words, so with many populating threads those words become heavily contended. Profiling a 64-core producer showed ~80% of HollowTypeWriteState.add CPU in this CAS loop (AtomicLongArray.compareAndSet via ThreadSafeBitSet.set).

Return early when the bit is already set. The read is a volatile read of the same word, so a bit set by another thread is observed and the method keeps its existing semantics; when the bit is not yet set the CAS loop is unchanged.

@arpitmalhotra arpitmalhotra changed the title ThreadSafeBitSet.set: skip the CAS when the bit is already set ThreadSafeBitSet.set: skip the CAS when the bit is already set (performance fix) Oct 9, 2026
@arpitmalhotra

Copy link
Copy Markdown
Author

@Sunjeet @jasonk000 can you please review this one. Thanks

@jasonk000

Copy link
Copy Markdown
Member

This looks great @arpitmalhotra

Can you extend this to clear() as well?

Luna flags similar issues in HollowSparseIntegerSet for set() line 351 and clear(), line 415, in both cases the we can check for value before doing the clone(), but this could be a separate PR.

…dy in the target state

HollowTypeWriteState.add() calls currentCyclePopulated.set(ordinal) for every
record written, including the common case where the same record is written
many times in a cycle (e.g. shared strings, tags or category ids referenced
from many collections). set() always performed a compareAndSet, even when the
bit was already set, which forces the cache line holding that word into the
exclusive state on every call.

For small, frequently referenced types the populated-ordinals bitset is only a
few words, so with many populating threads those words become heavily
contended. Profiling a 64-core producer showed ~80% of HollowTypeWriteState.add
CPU in this CAS loop (AtomicLongArray.compareAndSet via ThreadSafeBitSet.set).

Return early from set() when the bit is already set, and from clear() when the
bit is already clear. The read is a volatile read of the same word, so a change
made by another thread is observed and both methods keep their existing
semantics; when the bit does need to change the CAS loop is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@arpitmalhotra
arpitmalhotra force-pushed the fix/threadsafebitset-skip-cas-when-set branch from 15f1023 to 7e75011 Compare October 13, 2026 23:42
@arpitmalhotra

arpitmalhotra commented Oct 13, 2026 •

Copy link
Copy Markdown
Author

Thanks @jasonk000, updated to cover the clear() too. Also opened a separate PR for the HollowSparseIntegerSet https://github.com/Netflix/hollow/pull/872/changes. Thanks for flagging

@Sunjeet
Sunjeet merged commit 1b94407 into Netflix:master Oct 14, 2026
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.

4 participants