Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modify Capacity growth rate to be linear instead of doubling on resize. #75708

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.Collections.Internal;
using Xunit;

namespace Microsoft.CodeAnalysis.UnitTests.Collections
Expand All @@ -24,9 +25,9 @@ public static IEnumerable<object[]> TestLengthsAndSegmentCounts
yield return new object[] { 1, segmentsToAdd };
yield return new object[] { 10, segmentsToAdd };
yield return new object[] { 100, segmentsToAdd };
yield return new object[] { SegmentedArray<object>.TestAccessor.SegmentSize / 2, segmentsToAdd };
yield return new object[] { SegmentedArray<object>.TestAccessor.SegmentSize, segmentsToAdd };
yield return new object[] { SegmentedArray<object>.TestAccessor.SegmentSize * 2, segmentsToAdd };
yield return new object[] { SegmentedArray<T>.TestAccessor.SegmentSize / 2, segmentsToAdd };
yield return new object[] { SegmentedArray<T>.TestAccessor.SegmentSize, segmentsToAdd };
yield return new object[] { SegmentedArray<T>.TestAccessor.SegmentSize * 2, segmentsToAdd };
yield return new object[] { 100000, segmentsToAdd };
}
}
Expand Down Expand Up @@ -63,28 +64,29 @@ public void Capacity_MatchesSizeRequested(int initialCapacity, int requestedCapa
[MemberData(nameof(TestLengthsAndSegmentCounts))]
public void Capacity_ReusesSegments(int initialCapacity, int segmentCountToAdd)
{
var elementCountToAdd = segmentCountToAdd * SegmentedArray<object>.TestAccessor.SegmentSize;
var elementCountToAdd = segmentCountToAdd * SegmentedArray<T>.TestAccessor.SegmentSize;

var segmented = new SegmentedList<object>(initialCapacity);
var segmented = new SegmentedList<T>(initialCapacity);

var oldSegments = SegmentedCollectionsMarshal.AsSegments(segmented.GetTestAccessor().Items);
var oldSegmentCount = oldSegments.Length;
var oldSegmentCount = (segmented.Capacity + SegmentedArrayHelper.GetSegmentSize<T>() - 1) >> SegmentedArrayHelper.GetSegmentShift<T>();

segmented.Capacity = initialCapacity + elementCountToAdd;

var resizedSegments = SegmentedCollectionsMarshal.AsSegments(segmented.GetTestAccessor().Items);
var resizedSegmentCount = resizedSegments.Length;

Assert.Equal(oldSegmentCount + segmentCountToAdd, resizedSegmentCount);
var resizedSegmentCount = (segmented.Capacity + SegmentedArrayHelper.GetSegmentSize<T>() - 1) >> SegmentedArrayHelper.GetSegmentShift<T>();

for (var i = 0; i < oldSegmentCount - 1; i++)
Assert.Same(resizedSegments[i], oldSegments[i]);

for (var i = oldSegmentCount - 1; i < resizedSegmentCount - 1; i++)
Assert.Equal(resizedSegments[i].Length, SegmentedArray<object>.TestAccessor.SegmentSize);
Assert.Equal(resizedSegments[i]!.Length, SegmentedArray<T>.TestAccessor.SegmentSize);

for (var i = resizedSegmentCount; i < resizedSegments.Length; i++)
Assert.Null(resizedSegments[i]);

Assert.NotSame(resizedSegments[resizedSegmentCount - 1], oldSegments[oldSegmentCount - 1]);
Assert.Equal(resizedSegments[resizedSegmentCount - 1].Length, oldSegments[oldSegmentCount - 1].Length);
Assert.Equal(resizedSegments[resizedSegmentCount - 1]!.Length, oldSegments[oldSegmentCount - 1]!.Length);
}

[Theory]
Expand All @@ -93,7 +95,7 @@ public void Capacity_InOnlySingleSegment(
[CombinatorialValues(1, 2, 10, 100)] int initialCapacity,
[CombinatorialValues(1, 2, 10, 100)] int addItemCount)
{
var segmented = new SegmentedList<object>(initialCapacity);
var segmented = new SegmentedList<T>(initialCapacity);

var oldSegments = SegmentedCollectionsMarshal.AsSegments(segmented.GetTestAccessor().Items);

Expand All @@ -104,7 +106,7 @@ public void Capacity_InOnlySingleSegment(
Assert.Equal(1, oldSegments.Length);
Assert.Equal(1, resizedSegments.Length);
Assert.Same(resizedSegments[0], oldSegments[0]);
Assert.Equal(segmented.Capacity, resizedSegments[0].Length);
Assert.Equal(segmented.Capacity, resizedSegments[0]!.Length);
}

[Theory]
Expand Down Expand Up @@ -136,5 +138,54 @@ public void EnsureCapacity_MatchesSizeWithLargeCapacityRequest(int segmentCount)
list.EnsureCapacity(requestedCapacity);
Assert.Equal(requestedCapacity, list.Capacity);
}

[Theory]
[InlineData(1)]
[InlineData(2)]
[InlineData(4)]
public void EnsureCapacity_FullSegmentGrowsBySegment(int segmentCount)
{
var elementCount = segmentCount * SegmentedArray<T>.TestAccessor.SegmentSize;
var list = new SegmentedList<T>(elementCount);

Assert.Equal(elementCount, list.Capacity);

list.EnsureCapacity(elementCount + 1);
Assert.Equal((segmentCount + 1) * SegmentedArray<T>.TestAccessor.SegmentSize, list.Capacity);
}

[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(2)]
[InlineData(4)]
public void EnsureCapacity_HalfSegmentGrowsToMaxSegmentSize(int segmentCount)
{
var elementCount = segmentCount * SegmentedArray<T>.TestAccessor.SegmentSize + SegmentedArray<T>.TestAccessor.SegmentSize / 2;
var list = new SegmentedList<T>(elementCount);

Assert.Equal(elementCount, list.Capacity);

list.EnsureCapacity(elementCount + 1);
Assert.Equal((segmentCount + 1) * SegmentedArray<T>.TestAccessor.SegmentSize, list.Capacity);
}

[Theory]
[InlineData(1)]
[InlineData(2)]
[InlineData(4)]
public void EnsureCapacity_FullSegmentDoublesUnderlyingArraySize(int segmentCount)
{
var elementCount = segmentCount * SegmentedArray<T>.TestAccessor.SegmentSize;
var list = new SegmentedList<T>(elementCount);
var segments = SegmentedCollectionsMarshal.AsSegments(list.GetTestAccessor().Items);

Assert.Equal(segmentCount, segments.Length);

list.EnsureCapacity(elementCount + 1);
segments = SegmentedCollectionsMarshal.AsSegments(list.GetTestAccessor().Items);

Assert.Equal(2 * segmentCount, segments.Length);
}
}
}
38 changes: 19 additions & 19 deletions src/Dependencies/Collections/SegmentedArray.cs
Original file line number Diff line number Diff line change
Expand Up @@ -450,16 +450,16 @@ public AlignedSegmentEnumerator<T> GetEnumerator()

private struct AlignedSegmentEnumerator<T>
{
private readonly T[][] _firstSegments;
private readonly T[]?[] _firstSegments;
private readonly int _firstOffset;
private readonly T[][] _secondSegments;
private readonly T[]?[] _secondSegments;
private readonly int _secondOffset;
private readonly int _length;

private int _completed;
private (Memory<T> first, Memory<T> second) _current;

public AlignedSegmentEnumerator(T[][] firstSegments, int firstOffset, T[][] secondSegments, int secondOffset, int length)
public AlignedSegmentEnumerator(T[]?[] firstSegments, int firstOffset, T[]?[] secondSegments, int secondOffset, int length)
{
_firstSegments = firstSegments;
_firstOffset = firstOffset;
Expand Down Expand Up @@ -488,8 +488,8 @@ public bool MoveNext()
var offset = _firstOffset & SegmentedArrayHelper.GetOffsetMask<T>();
Debug.Assert(offset == (_secondOffset & SegmentedArrayHelper.GetOffsetMask<T>()), "Aligned views must start at the same segment offset");

var firstSegment = _firstSegments[initialFirstSegment];
var secondSegment = _secondSegments[initialSecondSegment];
var firstSegment = _firstSegments[initialFirstSegment]!;
var secondSegment = _secondSegments[initialSecondSegment]!;
var remainingInSegment = firstSegment.Length - offset;
var currentSegmentLength = Math.Min(remainingInSegment, _length);
_current = (firstSegment.AsMemory().Slice(offset, currentSegmentLength), secondSegment.AsMemory().Slice(offset, currentSegmentLength));
Expand Down Expand Up @@ -555,16 +555,16 @@ public UnalignedSegmentEnumerable<T> Reverse()

private struct UnalignedSegmentEnumerator<T>
{
private readonly T[][] _firstSegments;
private readonly T[]?[] _firstSegments;
private readonly int _firstOffset;
private readonly T[][] _secondSegments;
private readonly T[]?[] _secondSegments;
private readonly int _secondOffset;
private readonly int _length;

private int _completed;
private (Memory<T> first, Memory<T> second) _current;

public UnalignedSegmentEnumerator(T[][] firstSegments, int firstOffset, T[][] secondSegments, int secondOffset, int length)
public UnalignedSegmentEnumerator(T[]?[] firstSegments, int firstOffset, T[]?[] secondSegments, int secondOffset, int length)
{
_firstSegments = firstSegments;
_firstOffset = firstOffset;
Expand All @@ -591,8 +591,8 @@ public bool MoveNext()
var firstOffset = (_completed + _firstOffset) & SegmentedArrayHelper.GetOffsetMask<T>();
var secondOffset = (_completed + _secondOffset) & SegmentedArrayHelper.GetOffsetMask<T>();

var firstSegment = _firstSegments[initialFirstSegment];
var secondSegment = _secondSegments[initialSecondSegment];
var firstSegment = _firstSegments[initialFirstSegment]!;
var secondSegment = _secondSegments[initialSecondSegment]!;
var remainingInFirstSegment = firstSegment.Length - firstOffset;
var remainingInSecondSegment = secondSegment.Length - secondOffset;
var currentSegmentLength = Math.Min(Math.Min(remainingInFirstSegment, remainingInSecondSegment), _length - _completed);
Expand All @@ -603,16 +603,16 @@ public bool MoveNext()

public struct Reverse
{
private readonly T[][] _firstSegments;
private readonly T[]?[] _firstSegments;
private readonly int _firstOffset;
private readonly T[][] _secondSegments;
private readonly T[]?[] _secondSegments;
private readonly int _secondOffset;
private readonly int _length;

private int _completed;
private (Memory<T> first, Memory<T> second) _current;

public Reverse(T[][] firstSegments, int firstOffset, T[][] secondSegments, int secondOffset, int length)
public Reverse(T[]?[] firstSegments, int firstOffset, T[]?[] secondSegments, int secondOffset, int length)
{
_firstSegments = firstSegments;
_firstOffset = firstOffset;
Expand Down Expand Up @@ -699,14 +699,14 @@ public SegmentEnumerable<T> Reverse()

private struct SegmentEnumerator<T>
{
private readonly T[][] _segments;
private readonly T[]?[] _segments;
private readonly int _offset;
private readonly int _length;

private int _completed;
private Memory<T> _current;

public SegmentEnumerator(T[][] segments, int offset, int length)
public SegmentEnumerator(T[]?[] segments, int offset, int length)
{
_segments = segments;
_offset = offset;
Expand All @@ -731,7 +731,7 @@ public bool MoveNext()
var firstSegment = _offset >> SegmentedArrayHelper.GetSegmentShift<T>();
var offset = _offset & SegmentedArrayHelper.GetOffsetMask<T>();

var segment = _segments[firstSegment];
var segment = _segments[firstSegment]!;
var remainingInSegment = segment.Length - offset;
_current = segment.AsMemory().Slice(offset, Math.Min(remainingInSegment, _length));
_completed = _current.Length;
Expand All @@ -748,14 +748,14 @@ public bool MoveNext()

public struct Reverse
{
private readonly T[][] _segments;
private readonly T[]?[] _segments;
private readonly int _offset;
private readonly int _length;

private int _completed;
private Memory<T> _current;

public Reverse(T[][] segments, int offset, int length)
public Reverse(T[]?[] segments, int offset, int length)
{
_segments = segments;
_offset = offset;
Expand All @@ -780,7 +780,7 @@ public bool MoveNext()
var firstSegment = _offset >> SegmentedArrayHelper.GetSegmentShift<T>();
var offset = _offset & SegmentedArrayHelper.GetOffsetMask<T>();

var segment = _segments[firstSegment];
var segment = _segments[firstSegment]!;
var remainingInSegment = segment.Length - offset;
_current = segment.AsMemory().Slice(offset, Math.Min(remainingInSegment, _length));
_completed = _current.Length;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ internal readonly partial struct SegmentedArray<T>
internal static class PrivateMarshal
{
/// <inheritdoc cref="SegmentedCollectionsMarshal.AsSegments{T}(SegmentedArray{T})"/>
public static T[][] AsSegments(SegmentedArray<T> array)
public static T[]?[] AsSegments(SegmentedArray<T> array)
=> array._items;

public static SegmentedArray<T> AsSegmentedArray(int length, T[][] segments)
public static SegmentedArray<T> AsSegmentedArray(int length, T[]?[] segments)
=> new SegmentedArray<T>(length, segments);
}
}
Loading