GC poll in BulkMoveWithWriteBarrier for small sizes
- Dominant language
- C#
- Stars
- 18.3k
- Forks
- 5.6k
- PR merge metrics
- PR metrics pending
Description
https://github.com/dotnet/runtime/pull/101761 introduced the use of bulk copies for structs with more than 4 references. And as far as I understand this calls out to BulkMoveWithWriteBarrier on all runtimes.
https://github.com/dotnet/runtime/blob/5965189292e128fa65b91847cf7d10a37f5232aa/src/libraries/System.Private.CoreLib/src/System/Buffer.cs#L161-L172
This method consistently comes up high in certain method profiles on my arm64 mac. My assumption is that the GC poll at the end isn't super cheap in comparison to the copy cost when this gets done for less than a cache line worth of data.
I recall the thread static lookup being more costly on NativeAOT too (and this code indeed runs with worse performance on NativeAOT, though there are many other reasons that could be the case).
The bulk write barriers are a net win, but do we even need the GC poll for such small copies? Considering that these copies were unpolled before. By my estimation the poll, doing the thread local lookup etc, likely costs the same as performing the small copy. Maybe using cache line size as the ceiling for emitting an unpolled variant is a decent heuristic?
You run into this poll with any code that materializes “large-ish” structs for storage in a field, such as heap-based enumerators storing to Current. Sometimes this happens in a generic context, but even when you know the concrete type it’s not always easy to work around. For example, much of the struct size might come from a ReadOnlySequence field. There isn’t a straightforward way to slice that type in place, and generally you want the containing structs to be readonly as well, augmenting a buffer view with a bit of metadata. Once the entire struct is readonly, you always need to do a full copy to store it into Current, at which point adding just one more reference field of your own triggers the bulk write optimization. Writing code in the straightforward way makes it easy to hit this in practice.
Is it worth an experiment to try an unpolled variant?
Contributor guide
Assessment
This issue has not been assessed yet.