Skip to content

Commit

Permalink
Modify segmented list to grow by growth rate.
Browse files Browse the repository at this point in the history
A growth rate of 2x matches the current code behavior. A growth rate of just over 1 matches the growth rate (1 segment) in the other PR (dotnet#75708).

All growth rates benchmarked: 1.000001, 1.1, 1.25, 1.5, 2

Obviously, the single segment growth rate is a non-starter without allowing null segments (which is the approach the other PR took).

The 2x rate matches current behavior, and is really only measured as a baseline. From my reading of this chart, it looks like 1.1 is the best of these choices.
  • Loading branch information
ToddGrun committed Nov 5, 2024
1 parent 9dcb2a3 commit 5fea8f0
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 21 deletions.
36 changes: 31 additions & 5 deletions src/Dependencies/Collections/SegmentedList`1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ internal class SegmentedList<T> : IList<T>, IList, IReadOnlyList<T>
private static readonly SegmentedArray<T> s_emptyArray = new(0);
private static IEnumerator<T>? s_emptyEnumerator;

public static double SegmentGrowthRate { get; set; } = 2.0;

// Constructs a SegmentedList. The list is initially empty and has a capacity
// of zero. Upon adding the first element to the list the capacity is
// increased to DefaultCapacity, and then increased in multiples of two
Expand Down Expand Up @@ -512,12 +514,36 @@ internal void Grow(int capacity)
{
Debug.Assert(_items.Length < capacity);

var newCapacity = _items.Length == 0 ? DefaultCapacity : 2 * _items.Length;
int newCapacity;

// Allow the list to grow to maximum possible capacity (~2G elements) before encountering overflow.
// Note that this check works even when _items.Length overflowed thanks to the (uint) cast
if ((uint)newCapacity > MaxLength)
newCapacity = MaxLength;
if (_items.Length < SegmentedArrayHelper.GetSegmentSize<T>() / 2)
{
// The array isn't near the maximum segment size. If the array is empty, the new capacity
// should be DefaultCapacity. Otherwise, the new capacity should be double the current array size.
newCapacity = _items.Length == 0 ? DefaultCapacity : _items.Length * 2;
}
else
{
var lastSegmentLength = _items.Length & SegmentedArrayHelper.GetOffsetMask<T>();
if (lastSegmentLength > 0)
{
// The last segment isn't fully sized, increase the new capacity such that it will be.
newCapacity = (_items.Length - lastSegmentLength) + SegmentedArrayHelper.GetSegmentSize<T>();
}
else
{
// The last segment is fully sized, increase the number of segments by the desired growth factor
var oldSegmentCount = (_items.Length + SegmentedArrayHelper.GetSegmentSize<T>() - 1) >> SegmentedArrayHelper.GetSegmentShift<T>();
var newSegmentCount = (int)Math.Ceiling(oldSegmentCount * SegmentGrowthRate);

newCapacity = SegmentedArrayHelper.GetSegmentSize<T>() * newSegmentCount;
}

// Allow the list to grow to maximum possible capacity (~2G elements) before encountering overflow.
// Note that this check works even when _items.Length overflowed thanks to the (uint) cast
if ((uint)newCapacity > MaxLength)
newCapacity = MaxLength;
}

// If the computed capacity is still less than specified, set to the original argument.
// Capacities exceeding Array.MaxLength will be surfaced as OutOfMemoryException by Array.Resize.
Expand Down
25 changes: 9 additions & 16 deletions src/Tools/IdeCoreBenchmarks/SegmentedListBenchmarks_Add.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,28 +38,21 @@ private void AddToList<T>(T item)
array.Add(item);
}

private struct LargeStruct
private struct MediumStruct
{
public int i1 { get; set; }
public int i2 { get; set; }
public int i3 { get; set; }
public int i4 { get; set; }
public int i5 { get; set; }
public int i6 { get; set; }
public int i7 { get; set; }
public int i8 { get; set; }
public int i9 { get; set; }
public int i10 { get; set; }
public int i11 { get; set; }
public int i12 { get; set; }
public int i13 { get; set; }
public int i14 { get; set; }
public int i15 { get; set; }
public int i16 { get; set; }
public int i17 { get; set; }
public int i18 { get; set; }
public int i19 { get; set; }
public int i20 { get; set; }
}

private struct LargeStruct
{
public MediumStruct s1 { get; set; }
public MediumStruct s2 { get; set; }
public MediumStruct s3 { get; set; }
public MediumStruct s4 { get; set; }
}

private struct EnormousStruct
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using BenchmarkDotNet.Attributes;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.Collections.Internal;

[MemoryDiagnoser]
public class SegmentedListBenchmarks_Add_SegmentCounts
{
[Params(16, 256, 4096, 65536)]
public int SegmentCount { get; set; }

[ParamsAllValues]
public bool AddExtraItem { get; set; }

[Params(1.000001, 1.1, 1.25, 1.5, 2)]
public double SegmentGrowthRate { get; set; }

[Benchmark]
public void AddObjectToList()
=> AddToList(new object());

[Benchmark]
public void AddLargeStructToList()
=> AddToList(new LargeStruct());

[Benchmark]
public void AddEnormousStructToList()
=> AddToList(new EnormousStruct());

private void AddToList<T>(T item)
{
SegmentedList<T>.SegmentGrowthRate = SegmentGrowthRate;

var count = SegmentCount * SegmentedArrayHelper.GetSegmentSize<T>();
if (AddExtraItem)
count++;

var array = new SegmentedList<T>();
for (var i = 0; i < count; i++)
array.Add(item);
}

private struct MediumStruct
{
public int i1 { get; set; }
public int i2 { get; set; }
public int i3 { get; set; }
public int i4 { get; set; }
public int i5 { get; set; }
}

private struct LargeStruct
{
public MediumStruct s1 { get; set; }
public MediumStruct s2 { get; set; }
public MediumStruct s3 { get; set; }
public MediumStruct s4 { get; set; }
}

private struct EnormousStruct
{
public LargeStruct s1 { get; set; }
public LargeStruct s2 { get; set; }
public LargeStruct s3 { get; set; }
public LargeStruct s4 { get; set; }
public LargeStruct s5 { get; set; }
public LargeStruct s6 { get; set; }
public LargeStruct s7 { get; set; }
public LargeStruct s8 { get; set; }
public LargeStruct s9 { get; set; }
public LargeStruct s10 { get; set; }
}
}

0 comments on commit 5fea8f0

Please sign in to comment.