Skip to content

Commit 10a0619

Browse files
authored
Optimize Queue<T>.Enumerator (#117341)
* Optimize Queue<T>.Enumerator * Address naming feedback
1 parent 34078bb commit 10a0619

File tree

3 files changed

+44
-58
lines changed

3 files changed

+44
-58
lines changed

src/libraries/System.Collections/tests/Generic/Queue/Queue.Generic.Tests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ protected override IEnumerable<T> GenericIEnumerableFactory(int count)
5151
protected override void CopyTo(IEnumerable<T> enumerable, T[] array, int index) => ((Queue<T>)enumerable).CopyTo(array, index);
5252
protected override bool Remove(IEnumerable<T> enumerable) => ((Queue<T>)enumerable).TryDequeue(out _);
5353
protected override bool Enumerator_Empty_UsesSingletonInstance => true;
54-
protected override bool Enumerator_Current_UndefinedOperation_Throws => true;
54+
protected override bool Enumerator_Empty_Current_UndefinedOperation_Throws => true;
5555
protected override bool Enumerator_Empty_ModifiedDuringEnumeration_ThrowsInvalidOperationException => false;
5656
protected override Type IGenericSharedAPI_CopyTo_IndexLargerThanArrayCount_ThrowType => typeof(ArgumentOutOfRangeException);
5757

src/libraries/System.Collections/tests/Generic/Queue/Queue.Tests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ protected override ICollection NonGenericICollectionFactory()
2727
return new Queue<string>();
2828
}
2929

30-
protected override bool Enumerator_Current_UndefinedOperation_Throws => true;
30+
protected override bool Enumerator_Empty_Current_UndefinedOperation_Throw => true;
3131

3232
protected override Type ICollection_NonGeneric_CopyTo_IndexLargerThanArrayCount_ThrowType => typeof(ArgumentOutOfRangeException);
3333

src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Queue.cs

Lines changed: 42 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -421,90 +421,76 @@ private void Grow(int capacity)
421421
public struct Enumerator : IEnumerator<T>,
422422
IEnumerator
423423
{
424-
private readonly Queue<T> _q;
424+
private readonly Queue<T> _queue;
425425
private readonly int _version;
426-
private int _index; // -1 = not started, -2 = ended/disposed
426+
private int _i;
427427
private T? _currentElement;
428428

429-
internal Enumerator(Queue<T> q)
429+
internal Enumerator(Queue<T> queue)
430430
{
431-
_q = q;
432-
_version = q._version;
433-
_index = -1;
431+
_queue = queue;
432+
_version = queue._version;
433+
_i = -1;
434434
_currentElement = default;
435435
}
436436

437437
public void Dispose()
438438
{
439-
_index = -2;
439+
_i = -2;
440440
_currentElement = default;
441441
}
442442

443443
public bool MoveNext()
444444
{
445-
if (_version != _q._version) ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
446-
447-
if (_index == -2)
448-
return false;
449-
450-
_index++;
451-
452-
if (_index == _q._size)
445+
if (_version != _queue._version)
453446
{
454-
// We've run past the last element
455-
_index = -2;
456-
_currentElement = default;
457-
return false;
447+
ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
458448
}
459449

460-
// Cache some fields in locals to decrease code size
461-
T[] array = _q._array;
462-
uint capacity = (uint)array.Length;
450+
Queue<T> q = _queue;
451+
int size = q._size;
463452

464-
// _index represents the 0-based index into the queue, however the queue
465-
// doesn't have to start from 0 and it may not even be stored contiguously in memory.
466-
467-
uint arrayIndex = (uint)(_q._head + _index); // this is the actual index into the queue's backing array
468-
if (arrayIndex >= capacity)
453+
int offset = _i + 1;
454+
if ((uint)offset < (uint)size)
469455
{
470-
// NOTE: Originally we were using the modulo operator here, however
471-
// on Intel processors it has a very high instruction latency which
472-
// was slowing down the loop quite a bit.
473-
// Replacing it with simple comparison/subtraction operations sped up
474-
// the average foreach loop by 2x.
475-
476-
arrayIndex -= capacity; // wrap around if needed
477-
}
456+
_i = offset;
478457

479-
_currentElement = array[arrayIndex];
480-
return true;
481-
}
458+
T[] array = q._array;
459+
int index = q._head + offset;
460+
if ((uint)index < (uint)array.Length)
461+
{
462+
_currentElement = array[index];
463+
}
464+
else
465+
{
466+
// The index has wrapped around the end of the array. Shift the index and then
467+
// get the current element. It is tempting to dedup this dereferencing with that
468+
// in the if block above, but the if block above avoids a bounds check for the
469+
// accesses that are in that portion, whereas these still incur it.
470+
index -= array.Length;
471+
_currentElement = array[index];
472+
}
482473

483-
public T Current
484-
{
485-
get
486-
{
487-
if (_index < 0)
488-
ThrowEnumerationNotStartedOrEnded();
489-
return _currentElement!;
474+
return true;
490475
}
491-
}
492476

493-
private void ThrowEnumerationNotStartedOrEnded()
494-
{
495-
Debug.Assert(_index == -1 || _index == -2);
496-
throw new InvalidOperationException(_index == -1 ? SR.InvalidOperation_EnumNotStarted : SR.InvalidOperation_EnumEnded);
477+
_i = -2;
478+
_currentElement = default;
479+
return false;
497480
}
498481

499-
object? IEnumerator.Current
500-
{
501-
get { return Current; }
502-
}
482+
public T Current => _currentElement!;
483+
484+
object? IEnumerator.Current => Current;
503485

504486
void IEnumerator.Reset()
505487
{
506-
if (_version != _q._version) ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
507-
_index = -1;
488+
if (_version != _queue._version)
489+
{
490+
ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
491+
}
492+
493+
_i = -1;
508494
_currentElement = default;
509495
}
510496
}

0 commit comments

Comments
 (0)