From 2356c40816eda5546cb23c34257ff3264c7fec15 Mon Sep 17 00:00:00 2001 From: Brant Burnett Date: Thu, 25 May 2023 16:04:58 -0400 Subject: [PATCH 1/6] Use Span to fill List in more ToList scenarios --- .../src/System/Linq/AppendPrepend.SpeedOpt.cs | 107 ++++++++++++++++-- .../System/Linq/DefaultIfEmpty.SpeedOpt.cs | 17 ++- .../src/System/Linq/Lookup.SpeedOpt.cs | 50 ++++---- .../System.Linq/src/System/Linq/Lookup.cs | 7 +- .../System/Linq/OrderedEnumerable.SpeedOpt.cs | 2 +- .../src/System/Linq/Select.SpeedOpt.cs | 29 +++-- .../src/System/Linq/SingleLinkedNode.cs | 17 ++- .../src/System/Linq/ToCollection.cs | 30 ++++- .../System.Linq/tests/SelectTests.cs | 14 +++ 9 files changed, 219 insertions(+), 54 deletions(-) diff --git a/src/libraries/System.Linq/src/System/Linq/AppendPrepend.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/AppendPrepend.SpeedOpt.cs index a058b6048c2c4d..350a15d4e0fdfb 100644 --- a/src/libraries/System.Linq/src/System/Linq/AppendPrepend.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/AppendPrepend.SpeedOpt.cs @@ -70,10 +70,11 @@ public override TSource[] ToArray() return array; } - public override List ToList() + private List LazyToList() { - int count = GetCount(onlyIfCheap: true); - List list = count == -1 ? new List() : new List(count); + Debug.Assert(GetCount(onlyIfCheap: true) == -1); + + var list = new List(); if (!_appending) { list.Add(_item); @@ -88,6 +89,49 @@ public override List ToList() return list; } + public override List ToList() + { + int count = GetCount(onlyIfCheap: true); + if (count == -1) + { + return LazyToList(); + } + + if (count == 1) + { + // If GetCount returns 1, then _source is empty and only _item should be returned + return ToSingleItemList(_item); + } + + var list = new List(count); + Span span = SetCountAndGetSpan(list, count); + int index; + if (_appending) + { + index = 0; + } + else + { + span[0] = _item; + index = 1; + } + + foreach (var item in _source) + { + span[index] = item; + ++index; + } + + if (_appending) + { + span[index] = _item; + ++index; // For the Debug.Assert, should be elided in release mode + } + + Debug.Assert(index == span.Length, "All list elements were not initialized."); + return list; + } + public override int GetCount(bool onlyIfCheap) { if (_source is IIListProvider listProv) @@ -128,11 +172,7 @@ private TSource[] LazyToArray() array[index++] = node.Item; } - index = array.Length - 1; - for (SingleLinkedNode? node = _appended; node != null; node = node.Linked) - { - array[index--] = node.Item; - } + _appended?.Fill(array.AsSpan(^_appendCount)); return array; } @@ -176,23 +216,66 @@ public override TSource[] ToArray() return array; } + private List LazyToList() + { + Debug.Assert(GetCount(onlyIfCheap: true) == -1); + + var list = new List(); + + if (_prepended != null) + { + Span span = SetCountAndGetSpan(list, _prependCount); + int index = 0; + for (SingleLinkedNode? node = _prepended; node != null; node = node.Linked) + { + span[index] = node.Item; + ++index; + } + } + + list.AddRange(_source); + + if (_appended != null) + { + int index = list.Count; + Span span = SetCountAndGetSpan(list, index + _appendCount); + _appended.Fill(span[index..]); + } + + return list; + } + public override List ToList() { int count = GetCount(onlyIfCheap: true); - List list = count == -1 ? new List() : new List(count); + if (count == -1) + { + return LazyToList(); + } + + var list = new List(count); + Span span = SetCountAndGetSpan(list, count); + int index = 0; for (SingleLinkedNode? node = _prepended; node != null; node = node.Linked) { - list.Add(node.Item); + list[index] = node.Item; + ++index; } - list.AddRange(_source); + foreach (var item in _source) + { + span[index] = item; + ++index; + } if (_appended != null) { - list.AddRange(_appended.ToArray(_appendCount)); + _appended.Fill(span[index..]); + index += _appendCount; // For the Debug.Assert, should be elided in release mode } + Debug.Assert(index == span.Length, "All list elements were not initialized."); return list; } diff --git a/src/libraries/System.Linq/src/System/Linq/DefaultIfEmpty.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/DefaultIfEmpty.SpeedOpt.cs index 4bda8b86cc1753..f3e20fa938ee2e 100644 --- a/src/libraries/System.Linq/src/System/Linq/DefaultIfEmpty.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/DefaultIfEmpty.SpeedOpt.cs @@ -18,10 +18,21 @@ public TSource[] ToArray() public List ToList() { - List list = _source.ToList(); - if (list.Count == 0) + List list; + if (_source is IIListProvider listProvider && listProvider.GetCount(onlyIfCheap: true) == 0) { - list.Add(_default); + // When _source is empty, the ToList() code path will generally allocate an empty list which must then + // be resized to accept the default item. When it is cheap to determine that it will be empty, directly + // allocate a single item list instead. + list = ToSingleItemList(_default); + } + else + { + list = _source.ToList(); + if (list.Count == 0) + { + list.Add(_default); + } } return list; diff --git a/src/libraries/System.Linq/src/System/Linq/Lookup.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/Lookup.SpeedOpt.cs index 5fc89fc52555b6..9073a0e0f1104b 100644 --- a/src/libraries/System.Linq/src/System/Linq/Lookup.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/Lookup.SpeedOpt.cs @@ -10,22 +10,16 @@ public partial class Lookup : IIListProvider[] IIListProvider>.ToArray() { - IGrouping[] array = new IGrouping[_count]; - int index = 0; - Grouping? g = _lastGrouping; - if (g != null) + IGrouping[] array; + if (_count > 0) { - do - { - g = g._next; - Debug.Assert(g != null); - - array[index] = g; - ++index; - } - while (g != _lastGrouping); + array = new IGrouping[_count]; + Fill(_lastGrouping, array); + } + else + { + array = Array.Empty>(); } - return array; } @@ -53,8 +47,24 @@ internal TResult[] ToArray(Func, TResult> r List> IIListProvider>.ToList() { - List> list = new List>(_count); - Grouping? g = _lastGrouping; + List> list; + if (_count > 0) + { + list = new List>(_count); + Fill(_lastGrouping, Enumerable.SetCountAndGetSpan(list, _count)); + } + else + { + list = new List>(); + } + + return list; + } + + private static void Fill(Grouping? lastGrouping, Span> results) + { + int index = 0; + Grouping? g = lastGrouping; if (g != null) { do @@ -62,12 +72,12 @@ List> IIListProvider>.ToList g = g._next; Debug.Assert(g != null); - list.Add(g); - } - while (g != _lastGrouping); + results[index] = g; + ++index; + } while (g != lastGrouping); } - return list; + Debug.Assert(index == results.Length, "All list elements were not initialized."); } int IIListProvider>.GetCount(bool onlyIfCheap) => _count; diff --git a/src/libraries/System.Linq/src/System/Linq/Lookup.cs b/src/libraries/System.Linq/src/System/Linq/Lookup.cs index 2ee2d329255eb9..4e40e9c2e695d2 100644 --- a/src/libraries/System.Linq/src/System/Linq/Lookup.cs +++ b/src/libraries/System.Linq/src/System/Linq/Lookup.cs @@ -154,15 +154,20 @@ internal List ToList(Func, TResult Grouping? g = _lastGrouping; if (g != null) { + Span span = Enumerable.SetCountAndGetSpan(list, _count); + int index = 0; do { g = g._next; Debug.Assert(g != null); g.Trim(); - list.Add(resultSelector(g._key, g._elements)); + span[index] = resultSelector(g._key, g._elements); + ++index; } while (g != _lastGrouping); + + Debug.Assert(index == _count, "All list elements were not initialized."); } return list; diff --git a/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.SpeedOpt.cs index e9cb982c2bc43c..5a1518392af2cf 100644 --- a/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.SpeedOpt.cs @@ -98,7 +98,7 @@ internal List ToList(int minIdx, int maxIdx) if (minIdx == maxIdx) { - return new List(1) { GetEnumerableSorter().ElementAt(buffer._items, count, minIdx) }; + return Enumerable.ToSingleItemList(GetEnumerableSorter().ElementAt(buffer._items, count, minIdx)); } List list = new List(maxIdx - minIdx + 1); diff --git a/src/libraries/System.Linq/src/System/Linq/Select.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/Select.SpeedOpt.cs index bfa8d83dbb9dcf..86c59dd35dc168 100644 --- a/src/libraries/System.Linq/src/System/Linq/Select.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/Select.SpeedOpt.cs @@ -613,13 +613,7 @@ private TResult[] PreallocatingToArray(int count) Debug.Assert(count == _source.GetCount(onlyIfCheap: true)); TResult[] array = new TResult[count]; - int index = 0; - foreach (TSource input in _source) - { - array[index] = _selector(input); - ++index; - } - + Fill(_source, array, _selector); return array; } @@ -642,20 +636,33 @@ public List ToList() { case -1: list = new List(); + foreach (TSource input in _source) + { + list.Add(_selector(input)); + } break; case 0: - return new List(); + list = new List(); + break; default: list = new List(count); + Fill(_source, SetCountAndGetSpan(list, count), _selector); break; } - foreach (TSource input in _source) + return list; + } + + private static void Fill(IPartition source, Span results, Func func) + { + int index = 0; + foreach (TSource item in source) { - list.Add(_selector(input)); + results[index] = func(item); + ++index; } - return list; + Debug.Assert(index == results.Length, "All list elements were not initialized."); } public int GetCount(bool onlyIfCheap) diff --git a/src/libraries/System.Linq/src/System/Linq/SingleLinkedNode.cs b/src/libraries/System.Linq/src/System/Linq/SingleLinkedNode.cs index 37a4a3a60c5582..208635dca820c2 100644 --- a/src/libraries/System.Linq/src/System/Linq/SingleLinkedNode.cs +++ b/src/libraries/System.Linq/src/System/Linq/SingleLinkedNode.cs @@ -92,15 +92,26 @@ public TSource[] ToArray(int count) Debug.Assert(count == GetCount()); TSource[] array = new TSource[count]; - int index = count; + Fill(array); + return array; + } + + /// + /// Fills a span with the items of this node's singly-linked list in reverse. + /// + /// The span to fill. Must be the precise size required. + public void Fill(Span span) + { + Debug.Assert(span.Length == GetCount()); + + int index = span.Length; for (SingleLinkedNode? node = this; node != null; node = node.Linked) { --index; - array[index] = node.Item; + span[index] = node.Item; } Debug.Assert(index == 0); - return array; } } } diff --git a/src/libraries/System.Linq/src/System/Linq/ToCollection.cs b/src/libraries/System.Linq/src/System/Linq/ToCollection.cs index 280ced4d34f629..ab96be710e842f 100644 --- a/src/libraries/System.Linq/src/System/Linq/ToCollection.cs +++ b/src/libraries/System.Linq/src/System/Linq/ToCollection.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Diagnostics; namespace System.Linq { @@ -255,14 +256,37 @@ private static TSource[] HashSetToArray(HashSet set) private static List HashSetToList(HashSet set) { - var result = new List(set.Count); + int count = set.Count; - foreach (TSource item in set) + List result; + if (count > 0) { - result.Add(item); + result = new List(count); + Span span = SetCountAndGetSpan(result, count); + + int index = 0; + foreach (TSource item in set) + { + span[index] = item; + ++index; + } + + Debug.Assert(index == span.Length, "All list elements were not initialized."); + } + else + { + result = new List(); } return result; } + + internal static List ToSingleItemList(TSource item) + { + List list = new List(1); + Span span = SetCountAndGetSpan(list, 1); + span[0] = item; + return list; + } } } diff --git a/src/libraries/System.Linq/tests/SelectTests.cs b/src/libraries/System.Linq/tests/SelectTests.cs index 20de7814b6bcca..1f709bf9caadaf 100644 --- a/src/libraries/System.Linq/tests/SelectTests.cs +++ b/src/libraries/System.Linq/tests/SelectTests.cs @@ -1107,6 +1107,13 @@ public void Select_SourceIsArrayTakeTake() Assert.Equal(new[] { 2 }, source.Take(10)); } + [Fact] + public void Select_SourceIsIPartitionToArray() + { + Assert.Equal(Array.Empty(), new List().Order().Select(i => i * 2).ToArray()); + Assert.Equal(new[] { 2, 4, 6, 8 }, new List { 1, 2, 3, 4 }.Order().Select(i => i * 2).ToArray()); + } + [Fact] public void Select_SourceIsListSkipTakeCount() { @@ -1134,6 +1141,13 @@ public void Select_SourceIsListSkipTakeToList() Assert.Empty(new List { 1, 2, 3, 4 }.Select(i => i * 2).Skip(8).ToList()); } + [Fact] + public void Select_SourceIsIPartitionToList() + { + Assert.Equal(Array.Empty(), new List().Order().Select(i => i * 2).ToList()); + Assert.Equal(new[] { 2, 4, 6, 8 }, new List { 1, 2, 3, 4 }.Order().Select(i => i * 2).ToList()); + } + [Theory] [MemberData(nameof(MoveNextAfterDisposeData))] public void MoveNextAfterDispose(IEnumerable source) From 35c9d4c6b79cf6ebd2f87dea9654873cd6af38e8 Mon Sep 17 00:00:00 2001 From: Brant Burnett Date: Sun, 28 May 2023 11:26:42 -0400 Subject: [PATCH 2/6] Optimize Append/Prepend changes --- .../src/System/Linq/AppendPrepend.SpeedOpt.cs | 106 ++---------------- .../src/System/Linq/SingleLinkedNode.cs | 20 +++- 2 files changed, 27 insertions(+), 99 deletions(-) diff --git a/src/libraries/System.Linq/src/System/Linq/AppendPrepend.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/AppendPrepend.SpeedOpt.cs index 350a15d4e0fdfb..5105d48bae6497 100644 --- a/src/libraries/System.Linq/src/System/Linq/AppendPrepend.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/AppendPrepend.SpeedOpt.cs @@ -70,32 +70,9 @@ public override TSource[] ToArray() return array; } - private List LazyToList() - { - Debug.Assert(GetCount(onlyIfCheap: true) == -1); - - var list = new List(); - if (!_appending) - { - list.Add(_item); - } - - list.AddRange(_source); - if (_appending) - { - list.Add(_item); - } - - return list; - } - public override List ToList() { int count = GetCount(onlyIfCheap: true); - if (count == -1) - { - return LazyToList(); - } if (count == 1) { @@ -103,32 +80,18 @@ public override List ToList() return ToSingleItemList(_item); } - var list = new List(count); - Span span = SetCountAndGetSpan(list, count); - int index; - if (_appending) - { - index = 0; - } - else - { - span[0] = _item; - index = 1; - } - - foreach (var item in _source) + List list = count == -1 ? new List() : new List(count); + if (!_appending) { - span[index] = item; - ++index; + list.Add(_item); } + list.AddRange(_source); if (_appending) { - span[index] = _item; - ++index; // For the Debug.Assert, should be elided in release mode + list.Add(_item); } - Debug.Assert(index == span.Length, "All list elements were not initialized."); return list; } @@ -166,13 +129,8 @@ private TSource[] LazyToArray() TSource[] array = builder.ToArray(); - int index = 0; - for (SingleLinkedNode? node = _prepended; node != null; node = node.Linked) - { - array[index++] = node.Item; - } - - _appended?.Fill(array.AsSpan(^_appendCount)); + _prepended?.Fill(array); + _appended?.FillReversed(array); return array; } @@ -216,21 +174,15 @@ public override TSource[] ToArray() return array; } - private List LazyToList() + public override List ToList() { - Debug.Assert(GetCount(onlyIfCheap: true) == -1); - - var list = new List(); + int count = GetCount(onlyIfCheap: true); + List list = count == -1 ? new List() : new List(count); if (_prepended != null) { Span span = SetCountAndGetSpan(list, _prependCount); - int index = 0; - for (SingleLinkedNode? node = _prepended; node != null; node = node.Linked) - { - span[index] = node.Item; - ++index; - } + _prepended.Fill(span); } list.AddRange(_source); @@ -239,43 +191,9 @@ private List LazyToList() { int index = list.Count; Span span = SetCountAndGetSpan(list, index + _appendCount); - _appended.Fill(span[index..]); - } - - return list; - } - - public override List ToList() - { - int count = GetCount(onlyIfCheap: true); - if (count == -1) - { - return LazyToList(); - } - - var list = new List(count); - Span span = SetCountAndGetSpan(list, count); - int index = 0; - - for (SingleLinkedNode? node = _prepended; node != null; node = node.Linked) - { - list[index] = node.Item; - ++index; - } - - foreach (var item in _source) - { - span[index] = item; - ++index; - } - - if (_appended != null) - { - _appended.Fill(span[index..]); - index += _appendCount; // For the Debug.Assert, should be elided in release mode + _appended.FillReversed(span); } - Debug.Assert(index == span.Length, "All list elements were not initialized."); return list; } diff --git a/src/libraries/System.Linq/src/System/Linq/SingleLinkedNode.cs b/src/libraries/System.Linq/src/System/Linq/SingleLinkedNode.cs index 208635dca820c2..7468973d2c77c0 100644 --- a/src/libraries/System.Linq/src/System/Linq/SingleLinkedNode.cs +++ b/src/libraries/System.Linq/src/System/Linq/SingleLinkedNode.cs @@ -92,26 +92,36 @@ public TSource[] ToArray(int count) Debug.Assert(count == GetCount()); TSource[] array = new TSource[count]; - Fill(array); + FillReversed(array); return array; } /// - /// Fills a span with the items of this node's singly-linked list in reverse. + /// Fills a start of a span with the items of this node's singly-linked list. /// /// The span to fill. Must be the precise size required. public void Fill(Span span) { - Debug.Assert(span.Length == GetCount()); + int index = 0; + for (SingleLinkedNode? node = this; node != null; node = node.Linked) + { + span[index] = node.Item; + index++; + } + } + /// + /// Fills the end of a span with the items of this node's singly-linked list in reverse. + /// + /// The span to fill. + public void FillReversed(Span span) + { int index = span.Length; for (SingleLinkedNode? node = this; node != null; node = node.Linked) { --index; span[index] = node.Item; } - - Debug.Assert(index == 0); } } } From 8e52f98becf591a46f89cb33aab7f93cf3ac5eb9 Mon Sep 17 00:00:00 2001 From: Brant Burnett Date: Mon, 5 Jun 2023 13:37:38 -0400 Subject: [PATCH 3/6] Update src/libraries/System.Linq/src/System/Linq/Lookup.SpeedOpt.cs Co-authored-by: Stephen Toub --- src/libraries/System.Linq/src/System/Linq/Lookup.SpeedOpt.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Linq/src/System/Linq/Lookup.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/Lookup.SpeedOpt.cs index 9073a0e0f1104b..60489a222d247f 100644 --- a/src/libraries/System.Linq/src/System/Linq/Lookup.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/Lookup.SpeedOpt.cs @@ -74,7 +74,8 @@ private static void Fill(Grouping? lastGrouping, Span Date: Mon, 5 Jun 2023 13:38:45 -0400 Subject: [PATCH 4/6] Update src/libraries/System.Linq/src/System/Linq/Lookup.SpeedOpt.cs Co-authored-by: Stephen Toub --- .../System.Linq/src/System/Linq/Lookup.SpeedOpt.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/libraries/System.Linq/src/System/Linq/Lookup.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/Lookup.SpeedOpt.cs index 60489a222d247f..2ef86a008493a8 100644 --- a/src/libraries/System.Linq/src/System/Linq/Lookup.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/Lookup.SpeedOpt.cs @@ -47,16 +47,11 @@ internal TResult[] ToArray(Func, TResult> r List> IIListProvider>.ToList() { - List> list; + var list = new List>(_count); if (_count > 0) { - list = new List>(_count); Fill(_lastGrouping, Enumerable.SetCountAndGetSpan(list, _count)); } - else - { - list = new List>(); - } return list; } From 0478d900c7bd6fe7e3e9b10e37fe95d0a8de9b88 Mon Sep 17 00:00:00 2001 From: Brant Burnett Date: Mon, 5 Jun 2023 13:42:24 -0400 Subject: [PATCH 5/6] Address feedback --- .../src/System/Linq/AppendPrepend.SpeedOpt.cs | 13 ++----------- .../src/System/Linq/DefaultIfEmpty.SpeedOpt.cs | 17 +++-------------- .../System.Linq/src/System/Linq/ToCollection.cs | 7 +------ 3 files changed, 6 insertions(+), 31 deletions(-) diff --git a/src/libraries/System.Linq/src/System/Linq/AppendPrepend.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/AppendPrepend.SpeedOpt.cs index 5105d48bae6497..63de8a52b848e8 100644 --- a/src/libraries/System.Linq/src/System/Linq/AppendPrepend.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/AppendPrepend.SpeedOpt.cs @@ -179,20 +179,11 @@ public override List ToList() int count = GetCount(onlyIfCheap: true); List list = count == -1 ? new List() : new List(count); - if (_prepended != null) - { - Span span = SetCountAndGetSpan(list, _prependCount); - _prepended.Fill(span); - } + _prepended?.Fill(SetCountAndGetSpan(list, _prependCount)); list.AddRange(_source); - if (_appended != null) - { - int index = list.Count; - Span span = SetCountAndGetSpan(list, index + _appendCount); - _appended.FillReversed(span); - } + _appended?.FillReversed(SetCountAndGetSpan(list, list.Count + _appendCount)); return list; } diff --git a/src/libraries/System.Linq/src/System/Linq/DefaultIfEmpty.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/DefaultIfEmpty.SpeedOpt.cs index f3e20fa938ee2e..4bda8b86cc1753 100644 --- a/src/libraries/System.Linq/src/System/Linq/DefaultIfEmpty.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/DefaultIfEmpty.SpeedOpt.cs @@ -18,21 +18,10 @@ public TSource[] ToArray() public List ToList() { - List list; - if (_source is IIListProvider listProvider && listProvider.GetCount(onlyIfCheap: true) == 0) + List list = _source.ToList(); + if (list.Count == 0) { - // When _source is empty, the ToList() code path will generally allocate an empty list which must then - // be resized to accept the default item. When it is cheap to determine that it will be empty, directly - // allocate a single item list instead. - list = ToSingleItemList(_default); - } - else - { - list = _source.ToList(); - if (list.Count == 0) - { - list.Add(_default); - } + list.Add(_default); } return list; diff --git a/src/libraries/System.Linq/src/System/Linq/ToCollection.cs b/src/libraries/System.Linq/src/System/Linq/ToCollection.cs index ab96be710e842f..b7dc614e7ee75b 100644 --- a/src/libraries/System.Linq/src/System/Linq/ToCollection.cs +++ b/src/libraries/System.Linq/src/System/Linq/ToCollection.cs @@ -258,10 +258,9 @@ private static List HashSetToList(HashSet set) { int count = set.Count; - List result; + var result = new List(count); if (count > 0) { - result = new List(count); Span span = SetCountAndGetSpan(result, count); int index = 0; @@ -273,10 +272,6 @@ private static List HashSetToList(HashSet set) Debug.Assert(index == span.Length, "All list elements were not initialized."); } - else - { - result = new List(); - } return result; } From 86d8799605a3a8e0df0bb6fa92b4b53ac36c910f Mon Sep 17 00:00:00 2001 From: Brant Burnett Date: Fri, 27 Oct 2023 15:20:25 -0400 Subject: [PATCH 6/6] Remove ToSingleItemList, seems like an overoptimization --- .../System.Linq/src/System/Linq/AppendPrepend.SpeedOpt.cs | 2 +- .../src/System/Linq/OrderedEnumerable.SpeedOpt.cs | 2 +- src/libraries/System.Linq/src/System/Linq/ToCollection.cs | 8 -------- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/libraries/System.Linq/src/System/Linq/AppendPrepend.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/AppendPrepend.SpeedOpt.cs index 63de8a52b848e8..caf83bee1fcaac 100644 --- a/src/libraries/System.Linq/src/System/Linq/AppendPrepend.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/AppendPrepend.SpeedOpt.cs @@ -77,7 +77,7 @@ public override List ToList() if (count == 1) { // If GetCount returns 1, then _source is empty and only _item should be returned - return ToSingleItemList(_item); + return new List(1) { _item }; } List list = count == -1 ? new List() : new List(count); diff --git a/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.SpeedOpt.cs b/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.SpeedOpt.cs index 5a1518392af2cf..e9cb982c2bc43c 100644 --- a/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.SpeedOpt.cs +++ b/src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.SpeedOpt.cs @@ -98,7 +98,7 @@ internal List ToList(int minIdx, int maxIdx) if (minIdx == maxIdx) { - return Enumerable.ToSingleItemList(GetEnumerableSorter().ElementAt(buffer._items, count, minIdx)); + return new List(1) { GetEnumerableSorter().ElementAt(buffer._items, count, minIdx) }; } List list = new List(maxIdx - minIdx + 1); diff --git a/src/libraries/System.Linq/src/System/Linq/ToCollection.cs b/src/libraries/System.Linq/src/System/Linq/ToCollection.cs index b7dc614e7ee75b..aaf9b86dc93a54 100644 --- a/src/libraries/System.Linq/src/System/Linq/ToCollection.cs +++ b/src/libraries/System.Linq/src/System/Linq/ToCollection.cs @@ -275,13 +275,5 @@ private static List HashSetToList(HashSet set) return result; } - - internal static List ToSingleItemList(TSource item) - { - List list = new List(1); - Span span = SetCountAndGetSpan(list, 1); - span[0] = item; - return list; - } } }