From d23a51ba2877a185499fb7eb938c6bd12a5fe6eb Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Mon, 25 Apr 2016 22:43:41 +0100 Subject: [PATCH 1/4] Use ArrayPool for OrderedEnumerable temp arrays --- .../src/System/Linq/OrderedEnumerable.cs | 56 ++++++++++++++++--- src/System.Linq/src/project.json | 3 + 2 files changed, 50 insertions(+), 9 deletions(-) diff --git a/src/System.Linq/src/System/Linq/OrderedEnumerable.cs b/src/System.Linq/src/System/Linq/OrderedEnumerable.cs index 44ae5e5a7290..c073c0bdd906 100644 --- a/src/System.Linq/src/System/Linq/OrderedEnumerable.cs +++ b/src/System.Linq/src/System/Linq/OrderedEnumerable.cs @@ -2,8 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Buffers; using System.Collections; using System.Collections.Generic; +using System.Threading; namespace System.Linq { @@ -26,10 +28,22 @@ public IEnumerator GetEnumerator() Buffer buffer = new Buffer(_source); if (buffer._count > 0) { - int[] map = SortedMap(buffer); - for (int i = 0; i < buffer._count; i++) + int[] map = null; + try + { + map = SortedMap(buffer); + for (int i = 0; i < buffer._count; i++) + { + yield return buffer._items[map[i]]; + } + } + finally { - yield return buffer._items[map[i]]; + var rentedMap = Interlocked.Exchange(ref map, null); + if (rentedMap != null) + { + ArrayPool.Shared.Return(rentedMap); + } } } } @@ -51,6 +65,8 @@ public TElement[] ToArray() array[i] = buffer._items[map[i]]; } + ArrayPool.Shared.Return(map); + return array; } @@ -66,6 +82,8 @@ public List ToList() { list.Add(buffer._items[map[i]]); } + + ArrayPool.Shared.Return(map); } return list; @@ -99,11 +117,23 @@ internal IEnumerator GetEnumerator(int minIdx, int maxIdx) } else { - int[] map = SortedMap(buffer, minIdx, maxIdx); - while (minIdx <= maxIdx) + int[] map = null; + try + { + map = SortedMap(buffer, minIdx, maxIdx); + while (minIdx <= maxIdx) + { + yield return buffer._items[map[minIdx]]; + ++minIdx; + } + } + finally { - yield return buffer._items[map[minIdx]]; - ++minIdx; + var rentedMap = Interlocked.Exchange(ref map, null); + if (rentedMap != null) + { + ArrayPool.Shared.Return(rentedMap); + } } } } @@ -137,6 +167,7 @@ internal TElement[] ToArray(int minIdx, int maxIdx) ++idx; ++minIdx; } + ArrayPool.Shared.Return(map); return array; } @@ -168,6 +199,8 @@ internal List ToList(int minIdx, int maxIdx) ++minIdx; } + ArrayPool.Shared.Return(map); + return list; } @@ -587,7 +620,7 @@ internal abstract class EnumerableSorter private int[] ComputeMap(TElement[] elements, int count) { ComputeKeys(elements, count); - int[] map = new int[count]; + int[] map = ArrayPool.Shared.Rent(count); for (int i = 0; i < count; i++) { map[i] = i; @@ -612,7 +645,12 @@ internal int[] Sort(TElement[] elements, int count, int minIdx, int maxIdx) internal TElement ElementAt(TElement[] elements, int count, int idx) { - return elements[QuickSelect(ComputeMap(elements, count), count - 1, idx)]; + int[] map = ComputeMap(elements, count); + var index = QuickSelect(map, count - 1, idx); + + ArrayPool.Shared.Return(map); + + return elements[index]; } private int CompareKeys(int index1, int index2) diff --git a/src/System.Linq/src/project.json b/src/System.Linq/src/project.json index da77e581e452..72ad09b19513 100644 --- a/src/System.Linq/src/project.json +++ b/src/System.Linq/src/project.json @@ -1,4 +1,7 @@ { + "dependencies": { + "System.Buffers": "4.0.0-rc3-24022-00" + }, "frameworks": { "netstandard1.5": { "dependencies": { From 98868100f822daa17b4480080993367842f67342 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Tue, 26 Apr 2016 21:39:06 +0100 Subject: [PATCH 2/4] Only use ArrayPool for >= 32 items --- .../src/System/Linq/OrderedEnumerable.cs | 144 +++++++++++++----- 1 file changed, 104 insertions(+), 40 deletions(-) diff --git a/src/System.Linq/src/System/Linq/OrderedEnumerable.cs b/src/System.Linq/src/System/Linq/OrderedEnumerable.cs index c073c0bdd906..3560d4bbf818 100644 --- a/src/System.Linq/src/System/Linq/OrderedEnumerable.cs +++ b/src/System.Linq/src/System/Linq/OrderedEnumerable.cs @@ -26,24 +26,49 @@ private int[] SortedMap(Buffer buffer, int minIdx, int maxIdx) public IEnumerator GetEnumerator() { Buffer buffer = new Buffer(_source); - if (buffer._count > 0) + if (buffer._count >= 32) { - int[] map = null; - try + return GetLargeEnumerator(buffer); + } + else if (buffer._count > 0) + { + return GetSmallEnumerator(buffer); + } + + return GetEmptyEnumerator(); + } + + private IEnumerator GetEmptyEnumerator() + { + yield break; + } + + private IEnumerator GetSmallEnumerator(Buffer buffer) + { + int[] map = SortedMap(buffer); + for (int i = 0; i < buffer._count; i++) + { + yield return buffer._items[map[i]]; + } + } + + private IEnumerator GetLargeEnumerator(Buffer buffer) + { + int[] map = null; + try + { + map = SortedMap(buffer); + for (int i = 0; i < buffer._count; i++) { - map = SortedMap(buffer); - for (int i = 0; i < buffer._count; i++) - { - yield return buffer._items[map[i]]; - } + yield return buffer._items[map[i]]; } - finally + } + finally + { + var rentedMap = Interlocked.Exchange(ref map, null); + if (rentedMap != null) { - var rentedMap = Interlocked.Exchange(ref map, null); - if (rentedMap != null) - { - ArrayPool.Shared.Return(rentedMap); - } + ArrayPool.Shared.Return(rentedMap); } } } @@ -65,7 +90,10 @@ public TElement[] ToArray() array[i] = buffer._items[map[i]]; } - ArrayPool.Shared.Return(map); + if (count >= 32) + { + ArrayPool.Shared.Return(map); + } return array; } @@ -83,7 +111,10 @@ public List ToList() list.Add(buffer._items[map[i]]); } - ArrayPool.Shared.Return(map); + if (count >= 32) + { + ArrayPool.Shared.Return(map); + } } return list; @@ -113,32 +144,55 @@ internal IEnumerator GetEnumerator(int minIdx, int maxIdx) if (minIdx == maxIdx) { - yield return GetEnumerableSorter().ElementAt(buffer._items, count, minIdx); + return GetSingleEnumerator(buffer, count, minIdx); } - else + else if (count >= 32) { - int[] map = null; - try - { - map = SortedMap(buffer, minIdx, maxIdx); - while (minIdx <= maxIdx) - { - yield return buffer._items[map[minIdx]]; - ++minIdx; - } - } - finally - { - var rentedMap = Interlocked.Exchange(ref map, null); - if (rentedMap != null) - { - ArrayPool.Shared.Return(rentedMap); - } - } + return GetLargeEnumerator(buffer, count, minIdx, maxIdx); } + + return GetSmallEnumerator(buffer, count, minIdx, maxIdx); } + + return GetEmptyEnumerator(); + } + + private IEnumerator GetSingleEnumerator(Buffer buffer, int count, int minIdx) + { + yield return GetEnumerableSorter().ElementAt(buffer._items, count, minIdx); } + private IEnumerator GetSmallEnumerator(Buffer buffer, int count, int minIdx, int maxIdx) + { + int[] map = SortedMap(buffer, minIdx, maxIdx); + while (minIdx <= maxIdx) + { + yield return buffer._items[map[minIdx]]; + ++minIdx; + } + } + + private IEnumerator GetLargeEnumerator(Buffer buffer, int count, int minIdx, int maxIdx) + { + int[] map = null; + try + { + map = SortedMap(buffer, minIdx, maxIdx); + while (minIdx <= maxIdx) + { + yield return buffer._items[map[minIdx]]; + ++minIdx; + } + } + finally + { + var rentedMap = Interlocked.Exchange(ref map, null); + if (rentedMap != null) + { + ArrayPool.Shared.Return(rentedMap); + } + } + } internal TElement[] ToArray(int minIdx, int maxIdx) { Buffer buffer = new Buffer(_source); @@ -167,7 +221,11 @@ internal TElement[] ToArray(int minIdx, int maxIdx) ++idx; ++minIdx; } - ArrayPool.Shared.Return(map); + + if (idx >= 32) + { + ArrayPool.Shared.Return(map); + } return array; } @@ -199,7 +257,10 @@ internal List ToList(int minIdx, int maxIdx) ++minIdx; } - ArrayPool.Shared.Return(map); + if (list.Count >= 32) + { + ArrayPool.Shared.Return(map); + } return list; } @@ -620,7 +681,7 @@ internal abstract class EnumerableSorter private int[] ComputeMap(TElement[] elements, int count) { ComputeKeys(elements, count); - int[] map = ArrayPool.Shared.Rent(count); + int[] map = count >= 32 ? ArrayPool.Shared.Rent(count) : new int[count]; for (int i = 0; i < count; i++) { map[i] = i; @@ -648,7 +709,10 @@ internal TElement ElementAt(TElement[] elements, int count, int idx) int[] map = ComputeMap(elements, count); var index = QuickSelect(map, count - 1, idx); - ArrayPool.Shared.Return(map); + if (count >= 32) + { + ArrayPool.Shared.Return(map); + } return elements[index]; } From a9fbca7464165477a3179dc6740fb4e23cae2639 Mon Sep 17 00:00:00 2001 From: Jon Hanna Date: Wed, 27 Apr 2016 11:28:48 +0100 Subject: [PATCH 3/4] Implement ordered enumerable enumerators via Iterator. --- src/System.Linq/src/System/Linq/Iterator.cs | 2 +- .../src/System/Linq/OrderedEnumerable.cs | 214 +++++++----------- 2 files changed, 81 insertions(+), 135 deletions(-) diff --git a/src/System.Linq/src/System/Linq/Iterator.cs b/src/System.Linq/src/System/Linq/Iterator.cs index 7425046e2648..421b3efd7906 100644 --- a/src/System.Linq/src/System/Linq/Iterator.cs +++ b/src/System.Linq/src/System/Linq/Iterator.cs @@ -11,7 +11,7 @@ public static partial class Enumerable { internal abstract class Iterator : IEnumerable, IEnumerator { - private readonly int _threadId; + protected readonly int _threadId; internal int _state; internal TSource _current; diff --git a/src/System.Linq/src/System/Linq/OrderedEnumerable.cs b/src/System.Linq/src/System/Linq/OrderedEnumerable.cs index 3560d4bbf818..db19f92a247c 100644 --- a/src/System.Linq/src/System/Linq/OrderedEnumerable.cs +++ b/src/System.Linq/src/System/Linq/OrderedEnumerable.cs @@ -9,66 +9,80 @@ namespace System.Linq { - internal abstract class OrderedEnumerable : IOrderedEnumerable, IPartition + internal abstract class OrderedEnumerable : Enumerable.Iterator, IOrderedEnumerable, IPartition { + private const int ArrayPoolUseThreshold = 32; internal IEnumerable _source; + private Buffer _buffer; + private int _index; + private int _maxIdx = int.MaxValue; + private int[] _map; - private int[] SortedMap(Buffer buffer) + private int[] SortedMap(Buffer buffer, bool rent) { - return GetEnumerableSorter().Sort(buffer._items, buffer._count); + return GetEnumerableSorter().Sort(buffer._items, buffer._count, rent); } - private int[] SortedMap(Buffer buffer, int minIdx, int maxIdx) + private int[] SortedMap(Buffer buffer, int minIdx, int maxIdx, bool rent) { - return GetEnumerableSorter().Sort(buffer._items, buffer._count, minIdx, maxIdx); + return GetEnumerableSorter().Sort(buffer._items, buffer._count, minIdx, maxIdx, rent); } - public IEnumerator GetEnumerator() + public override bool MoveNext() { - Buffer buffer = new Buffer(_source); - if (buffer._count >= 32) - { - return GetLargeEnumerator(buffer); - } - else if (buffer._count > 0) + switch (_state) { - return GetSmallEnumerator(buffer); - } - - return GetEmptyEnumerator(); - } - - private IEnumerator GetEmptyEnumerator() - { - yield break; - } + case 1: + Buffer buffer = new Buffer(_source); + if (buffer._count > 0) + { + if (_index != 0 || _maxIdx != int.MaxValue) + { + _map = SortedMap(buffer, _index, _maxIdx, buffer._count >= ArrayPoolUseThreshold); + } + else + { + _map = SortedMap(buffer, buffer._count >= ArrayPoolUseThreshold); + } + + _maxIdx = Math.Min(_maxIdx, buffer._count - 1); + _buffer = buffer; + _state = 2; + goto case 2; + } - private IEnumerator GetSmallEnumerator(Buffer buffer) - { - int[] map = SortedMap(buffer); - for (int i = 0; i < buffer._count; i++) - { - yield return buffer._items[map[i]]; + break; + case 2: + if (_index <= _maxIdx) + { + _current = _buffer._items[_map[_index]]; + ++_index; + return true; + } + break; } + + Dispose(); + return false; } - private IEnumerator GetLargeEnumerator(Buffer buffer) + public override void Dispose() { - int[] map = null; - try + base.Dispose(); + if (_map != null) { - map = SortedMap(buffer); - for (int i = 0; i < buffer._count; i++) + _buffer = default(Buffer); + if (_map.Length >= ArrayPoolUseThreshold) { - yield return buffer._items[map[i]]; + int[] rented = Interlocked.Exchange(ref _map, null); + if (rented != null) + { + ArrayPool.Shared.Return(rented); + } } - } - finally - { - var rentedMap = Interlocked.Exchange(ref map, null); - if (rentedMap != null) + else { - ArrayPool.Shared.Return(rentedMap); + _map = null; } } } @@ -84,17 +98,13 @@ public TElement[] ToArray() } TElement[] array = new TElement[count]; - int[] map = SortedMap(buffer); + int[] map = SortedMap(buffer, true); for (int i = 0; i != array.Length; i++) { array[i] = buffer._items[map[i]]; } - if (count >= 32) - { - ArrayPool.Shared.Return(map); - } - + ArrayPool.Shared.Return(map); return array; } @@ -105,16 +115,13 @@ public List ToList() List list = new List(count); if (count > 0) { - int[] map = SortedMap(buffer); + int[] map = SortedMap(buffer, true); for (int i = 0; i != count; i++) { list.Add(buffer._items[map[i]]); } - if (count >= 32) - { - ArrayPool.Shared.Return(map); - } + ArrayPool.Shared.Return(map); } return list; @@ -133,66 +140,13 @@ public int GetCount(bool onlyIfCheap) internal IEnumerator GetEnumerator(int minIdx, int maxIdx) { - Buffer buffer = new Buffer(_source); - int count = buffer._count; - if (count > minIdx) - { - if (count <= maxIdx) - { - maxIdx = count - 1; - } - - if (minIdx == maxIdx) - { - return GetSingleEnumerator(buffer, count, minIdx); - } - else if (count >= 32) - { - return GetLargeEnumerator(buffer, count, minIdx, maxIdx); - } - - return GetSmallEnumerator(buffer, count, minIdx, maxIdx); - } - - return GetEmptyEnumerator(); - } - - private IEnumerator GetSingleEnumerator(Buffer buffer, int count, int minIdx) - { - yield return GetEnumerableSorter().ElementAt(buffer._items, count, minIdx); + OrderedEnumerable enumerator = _state == 0 && _threadId == Environment.CurrentManagedThreadId ? this : (OrderedEnumerable)Clone(); + enumerator._state = 1; + enumerator._index = minIdx; + enumerator._maxIdx = maxIdx; + return enumerator; } - private IEnumerator GetSmallEnumerator(Buffer buffer, int count, int minIdx, int maxIdx) - { - int[] map = SortedMap(buffer, minIdx, maxIdx); - while (minIdx <= maxIdx) - { - yield return buffer._items[map[minIdx]]; - ++minIdx; - } - } - - private IEnumerator GetLargeEnumerator(Buffer buffer, int count, int minIdx, int maxIdx) - { - int[] map = null; - try - { - map = SortedMap(buffer, minIdx, maxIdx); - while (minIdx <= maxIdx) - { - yield return buffer._items[map[minIdx]]; - ++minIdx; - } - } - finally - { - var rentedMap = Interlocked.Exchange(ref map, null); - if (rentedMap != null) - { - ArrayPool.Shared.Return(rentedMap); - } - } - } internal TElement[] ToArray(int minIdx, int maxIdx) { Buffer buffer = new Buffer(_source); @@ -212,7 +166,7 @@ internal TElement[] ToArray(int minIdx, int maxIdx) return new TElement[] { GetEnumerableSorter().ElementAt(buffer._items, count, minIdx) }; } - int[] map = SortedMap(buffer, minIdx, maxIdx); + int[] map = SortedMap(buffer, minIdx, maxIdx, true); TElement[] array = new TElement[maxIdx - minIdx + 1]; int idx = 0; while (minIdx <= maxIdx) @@ -222,11 +176,7 @@ internal TElement[] ToArray(int minIdx, int maxIdx) ++minIdx; } - if (idx >= 32) - { - ArrayPool.Shared.Return(map); - } - + ArrayPool.Shared.Return(map); return array; } @@ -249,7 +199,7 @@ internal List ToList(int minIdx, int maxIdx) return new List(1) { GetEnumerableSorter().ElementAt(buffer._items, count, minIdx) }; } - int[] map = SortedMap(buffer, minIdx, maxIdx); + int[] map = SortedMap(buffer, minIdx, maxIdx, true); List list = new List(maxIdx - minIdx + 1); while (minIdx <= maxIdx) { @@ -257,11 +207,7 @@ internal List ToList(int minIdx, int maxIdx) ++minIdx; } - if (list.Count >= 32) - { - ArrayPool.Shared.Return(map); - } - + ArrayPool.Shared.Return(map); return list; } @@ -575,6 +521,11 @@ internal OrderedEnumerable(IEnumerable source, Func ke _descending = descending; } + public override Enumerable.Iterator Clone() + { + return new OrderedEnumerable(_source, _keySelector, _comparer, _descending, _parent); + } + internal override EnumerableSorter GetEnumerableSorter(EnumerableSorter next) { EnumerableSorter sorter = new EnumerableSorter(_keySelector, _comparer, _descending, next); @@ -678,10 +629,10 @@ internal abstract class EnumerableSorter internal abstract int CompareAnyKeys(int index1, int index2); - private int[] ComputeMap(TElement[] elements, int count) + private int[] ComputeMap(TElement[] elements, int count, bool rent) { ComputeKeys(elements, count); - int[] map = count >= 32 ? ArrayPool.Shared.Rent(count) : new int[count]; + int[] map = rent ? ArrayPool.Shared.Rent(count) : new int[count]; for (int i = 0; i < count; i++) { map[i] = i; @@ -690,30 +641,25 @@ private int[] ComputeMap(TElement[] elements, int count) return map; } - internal int[] Sort(TElement[] elements, int count) + internal int[] Sort(TElement[] elements, int count, bool rent) { - int[] map = ComputeMap(elements, count); + int[] map = ComputeMap(elements, count, rent); QuickSort(map, 0, count - 1); return map; } - internal int[] Sort(TElement[] elements, int count, int minIdx, int maxIdx) + internal int[] Sort(TElement[] elements, int count, int minIdx, int maxIdx, bool rent) { - int[] map = ComputeMap(elements, count); + int[] map = ComputeMap(elements, count, rent); PartialQuickSort(map, 0, count - 1, minIdx, maxIdx); return map; } internal TElement ElementAt(TElement[] elements, int count, int idx) { - int[] map = ComputeMap(elements, count); + int[] map = ComputeMap(elements, count, true); var index = QuickSelect(map, count - 1, idx); - - if (count >= 32) - { - ArrayPool.Shared.Return(map); - } - + ArrayPool.Shared.Return(map); return elements[index]; } From 67179c18ea6a55346580df43dbc9aae3924578ef Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Thu, 19 May 2016 18:26:18 +0100 Subject: [PATCH 4/4] update ref --- src/System.Linq/src/project.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/System.Linq/src/project.json b/src/System.Linq/src/project.json index 72ad09b19513..e98a392e178b 100644 --- a/src/System.Linq/src/project.json +++ b/src/System.Linq/src/project.json @@ -1,6 +1,6 @@ { "dependencies": { - "System.Buffers": "4.0.0-rc3-24022-00" + "System.Buffers": "4.0.0-rc3-24117-00" }, "frameworks": { "netstandard1.5": {