From e41401ed9c1070971d2062e971fed14d1747be78 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 27 Nov 2018 09:27:06 -0500 Subject: [PATCH] Use local array in ConcurrentQueue for small perf improvement (#21192) Enqueue/TryDequeue/TryPeek are all repeatedly accessing the same array from a readonly field. Changing them to instead access that same array but from a cached local results in an ~10% throughput boost on a microbenchmark that does uncontended reads/writes of objects from/to the queue. (I also tried using a ref local to point directly to the target slot in the array, but that actually resulted in a measurable regression.) --- .../Concurrent/ConcurrentQueueSegment.cs | 24 ++++++++++++++-------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/System.Private.CoreLib/shared/System/Collections/Concurrent/ConcurrentQueueSegment.cs b/src/System.Private.CoreLib/shared/System/Collections/Concurrent/ConcurrentQueueSegment.cs index 24a172f..c706fae 100644 --- a/src/System.Private.CoreLib/shared/System/Collections/Concurrent/ConcurrentQueueSegment.cs +++ b/src/System.Private.CoreLib/shared/System/Collections/Concurrent/ConcurrentQueueSegment.cs @@ -128,6 +128,8 @@ namespace System.Collections.Concurrent /// Tries to dequeue an element from the queue. public bool TryDequeue(out T item) { + Slot[] slots = _slots; + // Loop in case of contention... var spinner = new SpinWait(); while (true) @@ -137,7 +139,7 @@ namespace System.Collections.Concurrent int slotsIndex = currentHead & _slotsMask; // Read the sequence number for the head position. - int sequenceNumber = Volatile.Read(ref _slots[slotsIndex].SequenceNumber); + int sequenceNumber = Volatile.Read(ref slots[slotsIndex].SequenceNumber); // We can dequeue from this slot if it's been filled by an enqueuer, which // would have left the sequence number at pos+1. @@ -156,14 +158,14 @@ namespace System.Collections.Concurrent { // Successfully reserved the slot. Note that after the above CompareExchange, other threads // trying to dequeue from this slot will end up spinning until we do the subsequent Write. - item = _slots[slotsIndex].Item; + item = slots[slotsIndex].Item; if (!Volatile.Read(ref _preservedForObservation)) { // If we're preserving, though, we don't zero out the slot, as we need it for // enumerations, peeking, ToArray, etc. And we don't update the sequence number, // so that an enqueuer will see it as full and be forced to move to a new segment. - _slots[slotsIndex].Item = default(T); - Volatile.Write(ref _slots[slotsIndex].SequenceNumber, currentHead + _slots.Length); + slots[slotsIndex].Item = default(T); + Volatile.Write(ref slots[slotsIndex].SequenceNumber, currentHead + slots.Length); } return true; } @@ -208,6 +210,8 @@ namespace System.Collections.Concurrent Interlocked.MemoryBarrier(); } + Slot[] slots = _slots; + // Loop in case of contention... var spinner = new SpinWait(); while (true) @@ -217,14 +221,14 @@ namespace System.Collections.Concurrent int slotsIndex = currentHead & _slotsMask; // Read the sequence number for the head position. - int sequenceNumber = Volatile.Read(ref _slots[slotsIndex].SequenceNumber); + int sequenceNumber = Volatile.Read(ref slots[slotsIndex].SequenceNumber); // We can peek from this slot if it's been filled by an enqueuer, which // would have left the sequence number at pos+1. int diff = sequenceNumber - (currentHead + 1); if (diff == 0) { - result = resultUsed ? _slots[slotsIndex].Item : default(T); + result = resultUsed ? slots[slotsIndex].Item : default(T); return true; } else if (diff < 0) @@ -261,6 +265,8 @@ namespace System.Collections.Concurrent /// public bool TryEnqueue(T item) { + Slot[] slots = _slots; + // Loop in case of contention... var spinner = new SpinWait(); while (true) @@ -270,7 +276,7 @@ namespace System.Collections.Concurrent int slotsIndex = currentTail & _slotsMask; // Read the sequence number for the tail position. - int sequenceNumber = Volatile.Read(ref _slots[slotsIndex].SequenceNumber); + int sequenceNumber = Volatile.Read(ref slots[slotsIndex].SequenceNumber); // The slot is empty and ready for us to enqueue into it if its sequence // number matches the slot. @@ -289,8 +295,8 @@ namespace System.Collections.Concurrent { // Successfully reserved the slot. Note that after the above CompareExchange, other threads // trying to return will end up spinning until we do the subsequent Write. - _slots[slotsIndex].Item = item; - Volatile.Write(ref _slots[slotsIndex].SequenceNumber, currentTail + 1); + slots[slotsIndex].Item = item; + Volatile.Write(ref slots[slotsIndex].SequenceNumber, currentTail + 1); return true; } } -- 2.7.4