-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Drop roslyn impact on scrolling perf by 30% #73648
Changes from all commits
14eece9
c5a38ad
5862e01
316f0a6
3a08e9e
79831fc
f6f7d32
1eba0d9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1198,7 +1198,7 @@ public void Sort(Comparison<T> comparison) | |
|
||
if (_size > 1) | ||
{ | ||
SegmentedArray.Sort<T>(_items, 0, _size, Comparer<T>.Create(comparison)); | ||
SegmentedArray.Sort(_items, 0, _size, comparison); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. existing codepath forced an allocation from comparison to comparer. That comparer was later converted back to a comparison by grabbing off the .Compare method from it. So this double allocated going through this path. THe path that takes an IComparer single-allocs (tearing off the .Compare delegate). Now, there is at least no alloc if passing in a comparison delegate. we pass it through all the way to the final place that uses it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
} | ||
_version++; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -90,7 +90,7 @@ public IEnumerable<ITagSpan<IClassificationTag>> GetTags(NormalizedSnapshotSpanC | |
} | ||
|
||
private static IEnumerable<ITagSpan<IClassificationTag>> GetIntersectingTags(NormalizedSnapshotSpanCollection spans, TagSpanIntervalTree<IClassificationTag> cachedTags) | ||
=> SegmentedListPool<ITagSpan<IClassificationTag>>.ComputeList( | ||
=> SegmentedListPool<TagSpan<IClassificationTag>>.ComputeList( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. switched everything to be TagSpan based. Avoids lots and lots and lots and lots of virtual dispatch. Everything is non-virtual on the impl type, allowing for major inlining. |
||
static (args, tags) => args.cachedTags.AddIntersectingTagSpans(args.spans, tags), | ||
(cachedTags, spans)); | ||
|
||
|
@@ -138,7 +138,7 @@ private IEnumerable<ITagSpan<IClassificationTag>> ComputeAndCacheAllTags( | |
var options = _globalOptions.GetClassificationOptions(document.Project.Language); | ||
|
||
// Final list of tags to produce, containing syntax/semantic/embedded classification tags. | ||
using var _ = SegmentedListPool.GetPooledList<ITagSpan<IClassificationTag>>(out var mergedTags); | ||
using var _ = SegmentedListPool.GetPooledList<TagSpan<IClassificationTag>>(out var mergedTags); | ||
|
||
_owner._threadingContext.JoinableTaskFactory.Run(async () => | ||
{ | ||
|
@@ -166,7 +166,7 @@ await TotalClassificationAggregateTagger.AddTagsAsync( | |
|
||
return GetIntersectingTags(spans, cachedTags); | ||
|
||
Func<NormalizedSnapshotSpanCollection, SegmentedList<ITagSpan<IClassificationTag>>, VoidResult, Task> GetTaggingFunction( | ||
Func<NormalizedSnapshotSpanCollection, SegmentedList<TagSpan<IClassificationTag>>, VoidResult, Task> GetTaggingFunction( | ||
bool requireSingleSpan, Func<TextSpan, SegmentedList<ClassifiedSpan>, Task> addTagsAsync) | ||
{ | ||
Contract.ThrowIfTrue(requireSingleSpan && spans.Count != 1, "We should only be asking for a single span"); | ||
|
@@ -175,7 +175,7 @@ Func<NormalizedSnapshotSpanCollection, SegmentedList<ITagSpan<IClassificationTag | |
|
||
async Task AddSpansAsync( | ||
NormalizedSnapshotSpanCollection spans, | ||
SegmentedList<ITagSpan<IClassificationTag>> result, | ||
SegmentedList<TagSpan<IClassificationTag>> result, | ||
Func<TextSpan, SegmentedList<ClassifiedSpan>, Task> addAsync) | ||
{ | ||
// temp buffer we can use across all our classification calls. Should be cleared between each call. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -51,8 +51,8 @@ public bool HasSpanThatContains(SnapshotPoint point) | |
return _tree.HasIntervalThatContains(point.Position, length: 0, new IntervalIntrospector(snapshot)); | ||
} | ||
|
||
public IList<ITagSpan<TTag>> GetIntersectingSpans(SnapshotSpan snapshotSpan) | ||
=> SegmentedListPool<ITagSpan<TTag>>.ComputeList( | ||
public IList<TagSpan<TTag>> GetIntersectingSpans(SnapshotSpan snapshotSpan) | ||
=> SegmentedListPool<TagSpan<TTag>>.ComputeList( | ||
static (args, tags) => [email protected](args.snapshotSpan, tags), | ||
(@this: this, snapshotSpan)); | ||
|
||
|
@@ -61,7 +61,7 @@ public IList<ITagSpan<TTag>> GetIntersectingSpans(SnapshotSpan snapshotSpan) | |
/// <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<ITagSpan<TTag>> result) | ||
private void AppendIntersectingSpansInSortedOrder(SnapshotSpan snapshotSpan, SegmentedList<TagSpan<TTag>> result) | ||
{ | ||
var snapshot = snapshotSpan.Snapshot; | ||
Debug.Assert(snapshot.TextBuffer == _textBuffer); | ||
|
@@ -80,14 +80,14 @@ public IEnumerable<ITagSpan<TTag>> GetSpans(ITextSnapshot snapshot) | |
public bool IsEmpty() | ||
=> _tree.IsEmpty(); | ||
|
||
public void AddIntersectingTagSpans(NormalizedSnapshotSpanCollection requestedSpans, SegmentedList<ITagSpan<TTag>> tags) | ||
public void AddIntersectingTagSpans(NormalizedSnapshotSpanCollection requestedSpans, SegmentedList<TagSpan<TTag>> tags) | ||
{ | ||
AddIntersectingTagSpansWorker(requestedSpans, tags); | ||
DebugVerifyTags(requestedSpans, tags); | ||
} | ||
|
||
[Conditional("DEBUG")] | ||
private static void DebugVerifyTags(NormalizedSnapshotSpanCollection requestedSpans, SegmentedList<ITagSpan<TTag>> tags) | ||
private static void DebugVerifyTags(NormalizedSnapshotSpanCollection requestedSpans, SegmentedList<TagSpan<TTag>> tags) | ||
{ | ||
if (tags == null) | ||
{ | ||
|
@@ -107,7 +107,7 @@ private static void DebugVerifyTags(NormalizedSnapshotSpanCollection requestedSp | |
|
||
private void AddIntersectingTagSpansWorker( | ||
NormalizedSnapshotSpanCollection requestedSpans, | ||
SegmentedList<ITagSpan<TTag>> tags) | ||
SegmentedList<TagSpan<TTag>> tags) | ||
{ | ||
const int MaxNumberOfRequestedSpans = 100; | ||
|
||
|
@@ -123,20 +123,20 @@ private void AddIntersectingTagSpansWorker( | |
|
||
private void AddTagsForSmallNumberOfSpans( | ||
NormalizedSnapshotSpanCollection requestedSpans, | ||
SegmentedList<ITagSpan<TTag>> tags) | ||
SegmentedList<TagSpan<TTag>> tags) | ||
{ | ||
foreach (var span in requestedSpans) | ||
AppendIntersectingSpansInSortedOrder(span, tags); | ||
} | ||
|
||
private void AddTagsForLargeNumberOfSpans(NormalizedSnapshotSpanCollection requestedSpans, SegmentedList<ITagSpan<TTag>> tags) | ||
private void AddTagsForLargeNumberOfSpans(NormalizedSnapshotSpanCollection requestedSpans, SegmentedList<TagSpan<TTag>> tags) | ||
{ | ||
// we are asked with bunch of spans. rather than asking same question again and again, ask once with big span | ||
// which will return superset of what we want. and then filter them out in O(m+n) cost. | ||
// m == number of requested spans, n = number of returned spans | ||
var mergedSpan = new SnapshotSpan(requestedSpans[0].Start, requestedSpans[^1].End); | ||
|
||
using var _1 = SegmentedListPool.GetPooledList<ITagSpan<TTag>>(out var tempList); | ||
using var _1 = SegmentedListPool.GetPooledList<TagSpan<TTag>>(out var tempList); | ||
|
||
AppendIntersectingSpansInSortedOrder(mergedSpan, tempList); | ||
if (tempList.Count == 0) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,15 +22,15 @@ internal abstract class EfficientTagger<TTag> : ITagger<TTag>, IDisposable where | |
/// <summary> | ||
/// Produce the set of tags with the requested <paramref name="spans"/>, adding those tags to <paramref name="tags"/>. | ||
/// </summary> | ||
public abstract void AddTags(NormalizedSnapshotSpanCollection spans, SegmentedList<ITagSpan<TTag>> tags); | ||
public abstract void AddTags(NormalizedSnapshotSpanCollection spans, SegmentedList<TagSpan<TTag>> tags); | ||
|
||
public abstract void Dispose(); | ||
|
||
/// <summary> | ||
/// Default impl of the core <see cref="ITagger{T}"/> interface. Forces an allocation. | ||
/// </summary> | ||
public IEnumerable<ITagSpan<TTag>> GetTags(NormalizedSnapshotSpanCollection spans) | ||
=> SegmentedListPool<ITagSpan<TTag>>.ComputeList( | ||
=> SegmentedListPool<TagSpan<TTag>>.ComputeList( | ||
static (args, tags) => [email protected](args.spans, tags), | ||
(@this: this, spans)); | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,8 +20,10 @@ | |
|
||
namespace Microsoft.CodeAnalysis.Classification; | ||
|
||
internal abstract class AbstractClassificationService : IClassificationService | ||
internal abstract class AbstractClassificationService(ISyntaxClassificationService syntaxClassificationService) : IClassificationService | ||
{ | ||
private readonly ISyntaxClassificationService _syntaxClassificationService = syntaxClassificationService; | ||
|
||
public abstract void AddLexicalClassifications(SourceText text, TextSpan textSpan, SegmentedList<ClassifiedSpan> result, CancellationToken cancellationToken); | ||
public abstract ClassifiedSpan AdjustStaleClassification(SourceText text, ClassifiedSpan classifiedSpan); | ||
|
||
|
@@ -192,8 +194,7 @@ public void AddSyntacticClassifications( | |
if (root is null) | ||
return; | ||
|
||
var classificationService = services.GetLanguageServices(root.Language).GetRequiredService<ISyntaxClassificationService>(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. service-retrieval continues to be surprising expensive when done in hot spots. here we can trivially lift this out such that thsi value is provided in the constructor. |
||
classificationService.AddSyntacticClassifications(root, textSpans, result, cancellationToken); | ||
_syntaxClassificationService.AddSyntacticClassifications(root, textSpans, result, cancellationToken); | ||
} | ||
|
||
/// <summary> | ||
|
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.
copy of method above, just taking and passing through a Comparison instead of an IComparer. See https://github.com/dotnet/roslyn/pull/73648/files#r1610644205 for more details.
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.
To better match the reference implementation (from dotnet/runtime), let's remove this method and inline these two lines to the caller:
roslyn/src/Dependencies/Collections/SegmentedArray.cs
Lines 408 to 409 in 1eba0d9