-
Notifications
You must be signed in to change notification settings - Fork 4k
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
Reduce lots of allocations in TagSpanIntervalTree #73715
Conversation
@@ -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); |
There was a problem hiding this comment.
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.
public ITextBuffer Buffer => _textBuffer; | ||
|
||
public SpanTrackingMode SpanTrackingMode => _spanTrackingMode; |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good idea!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Followup to #73713.
Moved to pooled collections, and callers pass in the collections they want to fill.
Before:
After: