From 0e0e2e84f8c283bdf15cd8e51d50303694e6443c Mon Sep 17 00:00:00 2001 From: Todd Grunke Date: Fri, 6 Oct 2023 18:22:53 -0700 Subject: [PATCH 1/2] Short-circuit tagger usage of interval tree when tagging complete snapshot Noticed when looking at CPU usage on prism for completion scenario. It looks like the primary culprit that I saw when debugging locally was outlining (TTag was IStructureTag2). I was seeing requests spanning the full snapshot with ~2000 entries in spansToInvalidate when editing in the languageparser.cs file in the Roslyn sln. The Except call would end up creating a 2000 entry set, and proceed to remove all 2000 entries from it. Addresses https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1897704 --- ...ctAsynchronousTaggerProvider.TagSource_ProduceTags.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs index 2c926b8403ff9..5387492f6adfb 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs @@ -397,7 +397,14 @@ private ImmutableDictionary> ComputeNewTa private IEnumerable> GetNonIntersectingTagSpans(IEnumerable spansToInvalidate, TagSpanIntervalTree oldTagTree) { - var snapshot = spansToInvalidate.First().Snapshot; + var firstSpanToInvalidate = spansToInvalidate.First(); + + // Performance: No need to fully realize spansToInvalidate or do any of the calculations below if the + // full snapshot is being invalidated. + if (firstSpanToInvalidate.Length == firstSpanToInvalidate.Snapshot.Length) + return Array.Empty>(); + + var snapshot = firstSpanToInvalidate.Snapshot; return oldTagTree.GetSpans(snapshot).Except( spansToInvalidate.SelectMany(oldTagTree.GetIntersectingSpans), From 96dfd8b8d71412c1a398c904a4b4f9e60308bee8 Mon Sep 17 00:00:00 2001 From: Todd Grunke Date: Fri, 6 Oct 2023 19:33:41 -0700 Subject: [PATCH 2/2] use the local --- ...stractAsynchronousTaggerProvider.TagSource_ProduceTags.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs index 5387492f6adfb..864bef4c23856 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs @@ -398,14 +398,13 @@ private ImmutableDictionary> ComputeNewTa private IEnumerable> GetNonIntersectingTagSpans(IEnumerable spansToInvalidate, TagSpanIntervalTree oldTagTree) { var firstSpanToInvalidate = spansToInvalidate.First(); + var snapshot = firstSpanToInvalidate.Snapshot; // Performance: No need to fully realize spansToInvalidate or do any of the calculations below if the // full snapshot is being invalidated. - if (firstSpanToInvalidate.Length == firstSpanToInvalidate.Snapshot.Length) + if (firstSpanToInvalidate.Length == snapshot.Length) return Array.Empty>(); - var snapshot = firstSpanToInvalidate.Snapshot; - return oldTagTree.GetSpans(snapshot).Except( spansToInvalidate.SelectMany(oldTagTree.GetIntersectingSpans), comparer: this);