Skip to content

ARC: optional cache-line isolation of the reference count - #403

Open
DTW-Thalion wants to merge 3 commits into
gnustep:masterfrom
DTW-Thalion:arc-refcount-isolation
Open

ARC: optional cache-line isolation of the reference count#403
DTW-Thalion wants to merge 3 commits into
gnustep:masterfrom
DTW-Thalion:arc-refcount-isolation

Conversation

@DTW-Thalion

Copy link
Copy Markdown
Contributor

Stacked on #399 and depends on it; the diff includes #399's commit until it merges.

Reference counts of distinct objects that share a cache line ping-pong between cores when those objects are retained and released concurrently, so unrelated objects contend even though they never share a reference.

This adds an opt-in -DOBJC_ALLOC_ALIGN=64 build that rounds each allocation up to a cache line, removing that false sharing at the cost of memory on the smallest objects. The default alignment is unchanged.

Opening as a draft while #399 is in review.

Comment thread gc_none.c Outdated
// retaining/releasing adjacent small objects ping-pong a shared line
// (measured: distinct-object retain/release at 4 threads 127ns -> 24ns).
//
// The cost is memory: cache-line alignment rounds every small allocation up to

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this is specific to the malloc implementation used. Handling aligned allocations of things that are not a multiple of the alignment size is painful, so snmalloc rounds up the size.

I am also curious how much of the false-sharing overhead is an artefact of the allocation policy. Do you know how snmalloc affects your workload with and without this relative to the baseline?

@DTW-Thalion DTW-Thalion Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question - so yesterday I wrote a test to try to answer that and I sent you an e-mail on the results. Short answer: snmalloc does not remove the cliff. When the objects are allocated together it packs the 16-byte instances onto shared cache lines the same as glibc, and is in fact worse at high thread counts. Its size-class rounding does not spread these apart.

@DTW-Thalion
DTW-Thalion force-pushed the arc-refcount-isolation branch 3 times, most recently from 79b1c79 to e4a58ca Compare July 24, 2026 13:24
Comment thread gc_none.c Outdated
_aligned_malloc(size, 32);
addr = _aligned_malloc(size, OBJC_ALLOC_ALIGN);
memset(addr, 0, size);
#elif OBJC_ALLOC_ALIGN > 16

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 16 here is hard coding an ABI detail. This probably should be if constexpr (OBJC_ALLOC_ALIGN > alignof(std::max_align_t))

@DTW-Thalion
DTW-Thalion force-pushed the arc-refcount-isolation branch 2 times, most recently from 8662de9 to 26aae97 Compare July 28, 2026 03:17
… CAS loop

The strong-retain and release fast paths spun a compare-exchange loop that
re-tried on every lost race, so under contention they wasted work that a single
read-modify-write instruction avoids.

A strong retain runs while the caller still owns a reference, so the object
cannot be at (or reach) the deallocating sentinel; its increment is therefore a
single fetch_add.  Release becomes a single fetch_sub, handling the
last-reference and saturation edges after the fact.  The weak-to-strong retain
keeps the compare-exchange loop, because it can race a concurrent final release
and has to check-and-increment atomically to avoid resurrecting a dying object.

Reserve the bit below the weak flag as a guard, so an optimistic increment can
never carry a saturating count into the weak flag.

FastRefCount.m mirrors the reference-count layout and is updated for the guard
bit; the saturation and weak-at-saturation cases it exercises still pass.

Measured on a 32-core machine: retain/release falls from 16.1 to 11.3 ns with no
contention, and a single shared object under 24 threads from 2143 to 1124 ns.
@DTW-Thalion
DTW-Thalion force-pushed the arc-refcount-isolation branch from 26aae97 to 6636c05 Compare July 28, 2026 03:23
@DTW-Thalion
DTW-Thalion force-pushed the arc-refcount-isolation branch from 6636c05 to 07503fb Compare July 28, 2026 11:28
@DTW-Thalion
DTW-Thalion marked this pull request as ready for review July 29, 2026 22:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants