diff --git a/src/libraries/System.Linq/src/System/Linq/Grouping.cs b/src/libraries/System.Linq/src/System/Linq/Grouping.cs index 6e19e4ab384e44..ecdb8a855d5aee 100644 --- a/src/libraries/System.Linq/src/System/Linq/Grouping.cs +++ b/src/libraries/System.Linq/src/System/Linq/Grouping.cs @@ -4,6 +4,7 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics; +using System.Runtime.CompilerServices; namespace System.Linq { @@ -173,8 +174,7 @@ public override bool MoveNext() ValidItem: _g = _g._next; Debug.Assert(_g is not null); - _g.Trim(); - _current = _resultSelector(_g.Key, _g._elements); + _current = _resultSelector(_g.Key, _g); return true; } } @@ -229,8 +229,7 @@ public override bool MoveNext() ValidItem: _g = _g._next; Debug.Assert(_g is not null); - _g.Trim(); - _current = _resultSelector(_g.Key, _g._elements); + _current = _resultSelector(_g.Key, _g); return true; } } @@ -335,7 +334,7 @@ public override bool MoveNext() Dispose(); return false; - ValidItem: + ValidItem: _g = _g._next; Debug.Assert(_g is not null); _current = _g; @@ -355,8 +354,9 @@ internal sealed class Grouping : IGrouping, ILis { internal readonly TKey _key; internal readonly int _hashCode; - internal TElement[] _elements; - internal int _count; + private GroupingElementArray _inlineElements; + private TElement[]? _elements; + private int _count; internal Grouping? _hashNext; internal Grouping? _next; @@ -364,32 +364,47 @@ internal Grouping(TKey key, int hashCode) { _key = key; _hashCode = hashCode; - _elements = new TElement[1]; } internal void Add(TElement element) { - if (_elements.Length == _count) - { - Array.Resize(ref _elements, checked(_count * 2)); - } + Span destination; - _elements[_count] = element; - _count++; - } + if (_elements is null) + { + destination = _inlineElements; - internal void Trim() - { - if (_elements.Length != _count) + if (_count == GroupingElementArray.Size) + { + _elements = new TElement[checked(_count * 2)]; + destination.CopyTo(_elements); + destination = _elements; + } + } + else { - Array.Resize(ref _elements, _count); + if (_elements.Length == _count) + { + Array.Resize(ref _elements, checked(_count * 2)); + } + + destination = _elements; } + + destination[_count++] = element; } public IEnumerator GetEnumerator() { Debug.Assert(_count > 0, "A grouping should only have been created if an element was being added to it."); - return new PartialArrayEnumerator(_elements, _count); + if (_elements is null) + { + return new GroupingElementArrayEnumerator(_inlineElements, _count); + } + else + { + return new PartialArrayEnumerator(_elements, _count); + } } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); @@ -404,14 +419,29 @@ public IEnumerator GetEnumerator() void ICollection.Clear() => ThrowHelper.ThrowNotSupportedException(); - bool ICollection.Contains(TElement item) => Array.IndexOf(_elements, item, 0, _count) >= 0; + bool ICollection.Contains(TElement item) + { + if (_elements is null) + { + return IndexOfInlineElements(item) >= 0; + } + + return Array.IndexOf(_elements, item) >= 0; + } - void ICollection.CopyTo(TElement[] array, int arrayIndex) => - Array.Copy(_elements, 0, array, arrayIndex, _count); + void ICollection.CopyTo(TElement[] array, int arrayIndex) => GetElements().CopyTo(array.AsSpan(arrayIndex)); bool ICollection.Remove(TElement item) => ThrowHelper.ThrowNotSupportedException_Boolean(); - int IList.IndexOf(TElement item) => Array.IndexOf(_elements, item, 0, _count); + int IList.IndexOf(TElement item) + { + if (_elements is null) + { + return IndexOfInlineElements(item); + } + + return Array.IndexOf(_elements, item, 0, _count); + } void IList.Insert(int index, TElement item) => ThrowHelper.ThrowNotSupportedException(); @@ -426,10 +456,74 @@ TElement IList.this[int index] ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index); } - return _elements[index]; + return GetElements()[index]; } set => ThrowHelper.ThrowNotSupportedException(); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ReadOnlySpan GetElements() + { + ReadOnlySpan buffer = _elements is null ? _inlineElements : _elements; + return buffer.Slice(0, _count); + } + + private int IndexOfInlineElements(TElement item) + { + ReadOnlySpan inlineElements = ((ReadOnlySpan)_inlineElements).Slice(0, _count); + for (int i = 0; i < inlineElements.Length; i++) + { + if (EqualityComparer.Default.Equals(inlineElements[i], item)) + { + return i; + } + } + + return -1; + } + + } + + [InlineArray(GroupingElementArray.Size)] + internal struct GroupingElementArray + { + public const int Size = 3; + private TElement _element0; + } + + internal sealed class GroupingElementArrayEnumerator : IEnumerator + { + private GroupingElementArray _array; + private int _count; + private int _index; + + public GroupingElementArrayEnumerator(GroupingElementArray array, int count) + { + Debug.Assert((uint)_count <= GroupingElementArray.Size); + _array = array; + _count = count; + _index = -1; + } + + public bool MoveNext() + { + if (_index + 1 < _count) + { + _index++; + return true; + } + + return false; + } + + public TElement Current => _array[_index]; + + object? IEnumerator.Current => Current; + + public void Dispose() { } + + public void Reset() => _index = -1; } + } diff --git a/src/libraries/System.Linq/src/System/Linq/Join.cs b/src/libraries/System.Linq/src/System/Linq/Join.cs index 677f6bd0eb8b36..f004462a0da33c 100644 --- a/src/libraries/System.Linq/src/System/Linq/Join.cs +++ b/src/libraries/System.Linq/src/System/Linq/Join.cs @@ -261,11 +261,10 @@ private static IEnumerable JoinIterator( Grouping? g = lookup.GetGrouping(outerKeySelector(item), create: false); if (g is not null) { - int count = g._count; - TInner[] elements = g._elements; + int count = g.GetElements().Length; for (int i = 0; i != count; ++i) { - yield return resultSelector(item, elements[i]); + yield return resultSelector(item, g.GetElements()[i]); } } } while (e.MoveNext()); diff --git a/src/libraries/System.Linq/src/System/Linq/LeftJoin.cs b/src/libraries/System.Linq/src/System/Linq/LeftJoin.cs index 097e2453d0167e..1367a3550fbfed 100644 --- a/src/libraries/System.Linq/src/System/Linq/LeftJoin.cs +++ b/src/libraries/System.Linq/src/System/Linq/LeftJoin.cs @@ -261,11 +261,10 @@ private static IEnumerable LeftJoinIterator(Func, TResult> r g = g._next; Debug.Assert(g is not null); - g.Trim(); - array[index] = resultSelector(g._key, g._elements); + array[index] = resultSelector(g._key, g); ++index; } while (g != _lastGrouping); diff --git a/src/libraries/System.Linq/src/System/Linq/Lookup.cs b/src/libraries/System.Linq/src/System/Linq/Lookup.cs index 7669aaaef7e190..6a5af4ae36d9f4 100644 --- a/src/libraries/System.Linq/src/System/Linq/Lookup.cs +++ b/src/libraries/System.Linq/src/System/Linq/Lookup.cs @@ -164,8 +164,7 @@ internal List ToList(Func, TResult g = g._next; Debug.Assert(g is not null); - g.Trim(); - span[index] = resultSelector(g._key, g._elements); + span[index] = resultSelector(g._key, g); ++index; } while (g != _lastGrouping); @@ -186,8 +185,7 @@ public IEnumerable ApplyResultSelector(Func RightJoinIterator