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

Reduce lots of allocations in TagSpanIntervalTree #73715

Merged
merged 33 commits into from
May 26, 2024

Conversation

CyrusNajmabadi
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi commented May 25, 2024

Followup to #73713.

Moved to pooled collections, and callers pass in the collections they want to fill.

Before:

image

After:

image

@CyrusNajmabadi CyrusNajmabadi requested a review from a team as a code owner May 25, 2024 20:53
@dotnet-issue-labeler dotnet-issue-labeler bot added Area-IDE untriaged Issues and PRs which have not yet been triaged by a lead labels May 25, 2024
@@ -159,8 +159,7 @@ public IEnumerable<TagSpan<IClassificationTag>> GetAllTags(NormalizedSnapshotSpa
arg: default).ConfigureAwait(false);
});

var cachedTags = new TagSpanIntervalTree<IClassificationTag>(snapshot.TextBuffer, SpanTrackingMode.EdgeExclusive, mergedTags);

var cachedTags = new TagSpanIntervalTree<IClassificationTag>(snapshot, SpanTrackingMode.EdgeExclusive, mergedTags);
Copy link
Member Author

Choose a reason for hiding this comment

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

changed the tree to take in the snapshot it initially corresponds to, not a random textbuffer it grabs CurrentSnapshot off of.

Comment on lines -58 to -60
public ITextBuffer Buffer => _textBuffer;

public SpanTrackingMode SpanTrackingMode => _spanTrackingMode;
Copy link
Member Author

Choose a reason for hiding this comment

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

removed these two properties. they are not needed or appropriate to expose off of this type.

@@ -141,7 +146,7 @@ private TagSpanIntervalTree<TTag> GetTagTree(ITextSnapshot snapshot, ImmutableDi
{
return tagTrees.TryGetValue(snapshot.TextBuffer, out var tagTree)
? tagTree
: new TagSpanIntervalTree<TTag>(snapshot.TextBuffer, _dataSource.SpanTrackingMode);
: TagSpanIntervalTree<TTag>.Empty;
Copy link
Member Author

Choose a reason for hiding this comment

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

singleton instead of alloc.

// For efficiency, just grab the old tags, remap them to the current snapshot, and place them in the
// newTags buffer. This is a safe mutation of this buffer as the caller doesn't use it after this point
// and instead immediately clears it.
oldTagTree.AddAllSpans(snapshot, newTags);
Copy link
Member Author

Choose a reason for hiding this comment

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

oogy, but safe, optimization.

}
else
{
// We do have spans to invalidate. Get the set of old tags that don't intersect with those and add the new tags.
using var _1 = _tagSpanSetPool.GetPooledObject(out var nonIntersectingTagSpans);
AddNonIntersectingTagSpans(spansToInvalidate, oldTagTree, nonIntersectingTagSpans);
Copy link
Member Author

Choose a reason for hiding this comment

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

inlined.

var latestSpans = latestTree.GetSpans(snapshot);
var previousSpans = previousTree.GetSpans(snapshot);
using var _1 = ArrayBuilder<TagSpan<TTag>>.GetInstance(out var latestSpans);
using var _2 = ArrayBuilder<TagSpan<TTag>>.GetInstance(out var previousSpans);
Copy link
Member Author

Choose a reason for hiding this comment

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

added pooling fo rthese as well. this also means the iteration is allocfree.

Copy link
Contributor

Choose a reason for hiding this comment

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

Why even mess with enumerators? We can just index directly into them now, right?

Copy link
Member Author

Choose a reason for hiding this comment

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

my god. you madman! will def change that :D

Copy link
Member Author

Choose a reason for hiding this comment

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

actually, it didn't make it much better afaict. i still need to track which builder and which index i'm working with. And... that's just an enumerator.

@@ -17,117 +20,126 @@ namespace Microsoft.CodeAnalysis.Editor.UnitTests.Tagging;
[UseExportProvider]
public class TagSpanIntervalTreeTests
{
private static TagSpanIntervalTree<ITextMarkerTag> CreateTree(string text, params Span[] spans)
private static (TagSpanIntervalTree<ITextMarkerTag>, ITextBuffer) CreateTree(string text, params Span[] spans)
Copy link
Member Author

Choose a reason for hiding this comment

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

just test code, mechanically updated now that the api changed.

static (args, tags) => [email protected](args.snapshotSpan, tags),
(@this: this, snapshotSpan));
public bool HasSpanThatIntersects(SnapshotPoint point)
=> _tree.HasIntervalThatIntersectsWith(point.Position, new IntervalIntrospector(point.Snapshot, _spanTrackingMode));
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: the ref highlighting tagger uses both the existing HasSpanThatContains and the new HasSpanThatIntersects. There are comments in it indicating this is intentional. i don't love this, as i think it's likely one could be removed. but i'm preserving behavior here.


/// <summary>
/// Gets all the spans that intersect with <paramref name="snapshotSpan"/> in sorted order and adds them to
/// <paramref name="result"/>. Note the sorted chunk of items are appended to <paramref name="result"/>. This
/// means that <paramref name="result"/> may not be sorted if there were already items in them.
/// </summary>
private void AppendIntersectingSpansInSortedOrder(SnapshotSpan snapshotSpan, SegmentedList<TagSpan<TTag>> result)
public void AddIntersectingTagSpans(SnapshotSpan snapshotSpan, SegmentedList<TagSpan<TTag>> result)
Copy link
Member Author

Choose a reason for hiding this comment

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

replacement for GetSpans. No allocs. Caller passes in the value the ywant to fill.

}

private TagSpan<TTag> GetTranslatedITagSpan(TagSpan<TTag> originalTagSpan, ITextSnapshot textSnapshot)
Copy link
Member Author

Choose a reason for hiding this comment

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

removed. no need for this (the interface version) and GetTranslatedTagSpan now that everything moved to being TagSpan based.


return spans.Count == 0
? NormalizedSnapshotSpanCollection.Empty
: new(spans);
Copy link
Contributor

Choose a reason for hiding this comment

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

new(spans);

nit: this ends up calling the IEnumerable ctor, right?

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! is tehre an alternative?

Copy link
Contributor

Choose a reason for hiding this comment

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

I guess not. There is an IList ctor for NormalizedSnaphotSpanCollection which doesn't use an enumerator, but it doesn't look like ArrayBuilder implements IList?

Copy link
Member Author

Choose a reason for hiding this comment

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

yeah, i can live with this for now :)

{
var (currentNode, firstTime) = tuple;
if (currentNode != null)
if (root == null)
Copy link
Contributor

Choose a reason for hiding this comment

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

if (root == null)

not sure if this is common, but this could be pulled outside the worker too

Copy link
Member Author

Choose a reason for hiding this comment

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

good idea!

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