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

Introduce an interval tree implementation backed by an array. #73855

Merged
merged 32 commits into from
Jun 5, 2024

Conversation

CyrusNajmabadi
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi commented Jun 5, 2024

Drops memory allocations for our interval tree by 2/3rds. From:

image

to:

image

The savings here come from the problem that the binary-tree approach needs an allocation for each node itself (so all the overhead there for all N nodes we create), and then each node contains 3 node pointers (to the left node, right node, and max end node), and an 'int' to represent height.

The new system has no Node allocation itself (the 'node' is now just a struct with the original client value, and an integer for hte max end node index). Instead, there is just one array alloc (so only the overhead of one object header for the array itself). There is no need for a left/right pointer as they can be inferred from the index of the node we are currently at.

@dotnet-issue-labeler dotnet-issue-labeler bot added Area-IDE untriaged Issues and PRs which have not yet been triaged by a lead labels Jun 5, 2024
using Xunit;

namespace Microsoft.CodeAnalysis.Editor.UnitTests.Collections;

public sealed class IntervalTreeTests
public abstract class IntervalTreeTests
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this allows me to run the same tests on both impls of interval trees. note: this test code is not pretty. I didn't want to spend a lot of time figuring out how to make it pretty.

[Fact]
public void TestProperBalancing()
{
for (var i = 0; i < 3000; i++)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hammers a ton of configurations of interval trees, ensuring that our creation of the complete binary tree is correct.

return (new TextSpanIntervalTree(interpolationInteriorSpans), new TextSpanIntervalTree(restrictedSpans));
return (
FlatArrayIntervalTree<TextSpan>.CreateFromUnsorted(new TextSpanIntervalIntrospector(), interpolationInteriorSpans),
FlatArrayIntervalTree<TextSpan>.CreateFromUnsorted(new TextSpanIntervalIntrospector(), restrictedSpans));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note; i'm usnig CreateFromUnsorted here as i don't want to have to prove yet if the above algorithm produces a sorted list of elements.

/// </list>
/// </remarks>
public static FlatArrayIntervalTree<T> CreateFromSorted<TIntrospector>(in TIntrospector introspector, SegmentedList<T> values)
where TIntrospector : struct, IIntervalIntrospector<T>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was the code from the binary tree version, moved over her.e

Note: it was changed extensively and should be reviewed. Specifically, we must generated a complete binary tree so that all nodes can find their children through the 2*i + 1 (or) 2 algorithm. If there are gaps anywhere (say multiple nodes on the second to last level that have a left child, but no right child) then this will break.


public sealed class BinaryIntervalTreeTests : IntervalTreeTests
{
private protected override IEnumerable<IIntervalTree<Tuple<int, int, string>>> CreateTrees(IEnumerable<Tuple<int, int, string>> values)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IEnumerable

Why does this return an IEnumerable?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't ask :'(

where TIntrospector : struct, IIntervalIntrospector<T>
=> introspector.GetSpan(value).End;

bool IIntervalTree<T>.Any<TIntrospector>(int start, int length, TestInterval<T, TIntrospector> testInterval, in TIntrospector introspector)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the next pr, this code will be shared with the binary tree impl.

int start, int length, TestInterval<T, TIntrospector> testInterval,
ref TemporaryArray<T> builder, in TIntrospector introspector,
bool stopAfterFirst)
{
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the next pr, this code will be shared with the binary tree impl.

// The nature of a complete tree is that the last level always only contains the odd remaining numbers.
// For example, given the initial values a-n:
//
// a, b, c, d, e, f, g, h, i, j, k, l, m, n. The final tree will look like:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The final tree will look like:

nit: consider putting on separate line from the tree values (same with the "Which corresponds to:" text on the line below)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will do in followup pr.

ref TemporaryArray<T> builder, in TIntrospector introspector,
bool stopAfterFirst)
{
var array = _array;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

var array = _array;

why the local?


candidates.Push((nodeIndex: 0, firstTime: true));

while (candidates.TryPop(out var currentTuple))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

while (candidates.TryPop(out var currentTuple))

From our offline conversation, might be nice if this and GetEnumerator shared code

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup. can do in followup.

Copy link
Contributor

@ToddGrun ToddGrun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-IDE untriaged Issues and PRs which have not yet been triaged by a lead
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants