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

made diagnostic tags to be removed faster and inserted slower. #8102

Merged
merged 3 commits into from
Jan 25, 2016
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ event EventHandler ITaggerEventSource.UIUpdatesResumed { add { } remove { } }
void ITaggerEventSource.Connect() { }
void ITaggerEventSource.Disconnect() { }

// we will show new tags to users very slowly.
// don't confused this with data changed event which is for tag producer (which is set to NearImmediate).
// this delay is for letting editor know about newly added tags.
protected override TaggerDelay NewTagsNotificationDelay => TaggerDelay.OnIdle;

protected override ITaggerEventSource CreateEventSource(ITextView textViewOpt, ITextBuffer subjectBuffer)
{
// We act as a source of events ourselves. When the diagnostics service tells
Expand Down Expand Up @@ -138,7 +143,11 @@ internal void OnDiagnosticsUpdated(DiagnosticsUpdatedArgs e, SourceText sourceTe
_latestSourceText = sourceText;
_latestEditorSnapshot = editorSnapshot;
}
this.Changed?.Invoke(this, new TaggerEventArgs(TaggerDelay.Medium));

// unlike any other tagger, actual work to produce data is done by other service rather than tag provider itself.
// so we don't need to do any big delay for diagnostic tagger (producer) to reduce doing expensive work repeatedly. that is already
// taken cared by the external service (diagnostic service).
this.Changed?.Invoke(this, new TaggerEventArgs(TaggerDelay.NearImmediate));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ private sealed partial class TagSource : ForegroundThreadAffinitizedObject

#endregion

public event Action<ICollection<KeyValuePair<ITextBuffer, NormalizedSnapshotSpanCollection>>> TagsChangedForBuffer;
public event Action<ICollection<KeyValuePair<ITextBuffer, DiffResult>>> TagsChangedForBuffer;

public event EventHandler Paused;
public event EventHandler Resumed;
Expand Down Expand Up @@ -112,6 +112,9 @@ public TagSource(
RecalculateTagsOnChanged(new TaggerEventArgs(TaggerDelay.Short));
}

public TaggerDelay NewTagsNotificationDelay => _dataSource.NewTagsNotificationDelay;
public TaggerDelay OldTagNotificationDelay => _dataSource.OldTagNotificationDelay;

private ITaggerEventSource CreateEventSource()
{
var eventSource = _dataSource.CreateEventSource(_textViewOpt, _subjectBuffer);
Expand Down Expand Up @@ -260,7 +263,7 @@ public void Disconnect()
_eventSource.Changed -= OnChanged;
}

private void RaiseTagsChanged(ITextBuffer buffer, NormalizedSnapshotSpanCollection difference)
private void RaiseTagsChanged(ITextBuffer buffer, DiffResult difference)
{
this.AssertIsForeground();
if (difference.Count == 0)
Expand All @@ -270,10 +273,10 @@ private void RaiseTagsChanged(ITextBuffer buffer, NormalizedSnapshotSpanCollecti
}

RaiseTagsChanged(SpecializedCollections.SingletonCollection(
new KeyValuePair<ITextBuffer, NormalizedSnapshotSpanCollection>(buffer, difference)));
new KeyValuePair<ITextBuffer, DiffResult>(buffer, difference)));
}

private void RaiseTagsChanged(ICollection<KeyValuePair<ITextBuffer, NormalizedSnapshotSpanCollection>> collection)
private void RaiseTagsChanged(ICollection<KeyValuePair<ITextBuffer, DiffResult>> collection)
{
TagsChangedForBuffer?.Invoke(collection);
}
Expand All @@ -296,9 +299,12 @@ private static T NextOrDefault<T>(IEnumerator<T> enumerator)
/// <summary>
/// Return all the spans that appear in only one of "latestSpans" or "previousSpans".
/// </summary>
private static IEnumerable<SnapshotSpan> Difference<T>(IEnumerable<ITagSpan<T>> latestSpans, IEnumerable<ITagSpan<T>> previousSpans, IEqualityComparer<T> comparer)
private static DiffResult Difference<T>(IEnumerable<ITagSpan<T>> latestSpans, IEnumerable<ITagSpan<T>> previousSpans, IEqualityComparer<T> comparer)
where T : ITag
{
List<SnapshotSpan> added = null;
Copy link
Member

Choose a reason for hiding this comment

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

much simpler to just 'new' these here. You could even pool them if you really wanted to since the DiffResult is going to make a copy of them in the constructor.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

the reason I put that there was most of time, one of tree will be 0 length. so no reason that create new list. and DiffResult check it being null and use Normalized ...Empty.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changed it to use pool. but I still think most of time, one of the list will be always 0

List<SnapshotSpan> removed = null;

var latestEnumerator = latestSpans.GetEnumerator();
var previousEnumerator = previousSpans.GetEnumerator();
try
Expand All @@ -313,12 +319,12 @@ private static IEnumerable<SnapshotSpan> Difference<T>(IEnumerable<ITagSpan<T>>

if (latestSpan.Start < previousSpan.Start)
{
yield return latestSpan;
AddToList(ref added, latestSpan);
latest = NextOrDefault(latestEnumerator);
}
else if (previousSpan.Start < latestSpan.Start)
{
yield return previousSpan;
AddToList(ref removed, previousSpan);
previous = NextOrDefault(previousEnumerator);
}
else
Expand All @@ -327,19 +333,19 @@ private static IEnumerable<SnapshotSpan> Difference<T>(IEnumerable<ITagSpan<T>>
// region to be conservative.
if (previousSpan.End > latestSpan.End)
{
yield return previousSpan;
AddToList(ref removed, previousSpan);
latest = NextOrDefault(latestEnumerator);
}
else if (latestSpan.End > previousSpan.End)
{
yield return latestSpan;
AddToList(ref added, latestSpan);
previous = NextOrDefault(previousEnumerator);
}
else
{
if (!comparer.Equals(latest.Tag, previous.Tag))
{
yield return latestSpan;
AddToList(ref added, latestSpan);
}

latest = NextOrDefault(latestEnumerator);
Expand All @@ -350,13 +356,13 @@ private static IEnumerable<SnapshotSpan> Difference<T>(IEnumerable<ITagSpan<T>>

while (latest != null)
{
yield return latest.Span;
AddToList(ref added, latest.Span);
latest = NextOrDefault(latestEnumerator);
}

while (previous != null)
{
yield return previous.Span;
AddToList(ref removed, previous.Span);
previous = NextOrDefault(previousEnumerator);
}
}
Expand All @@ -365,6 +371,14 @@ private static IEnumerable<SnapshotSpan> Difference<T>(IEnumerable<ITagSpan<T>>
latestEnumerator.Dispose();
previousEnumerator.Dispose();
}

return new DiffResult(added, removed);
}

private static void AddToList<T>(ref List<T> list, T item)
{
list = list ?? new List<T>();
list.Add(item);
Copy link
Member

Choose a reason for hiding this comment

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

This optimization seems unnecessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

why bad to have it?

Copy link
Member

Choose a reason for hiding this comment

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

Because it's a micro optimization that in general doesn't seem like it will do anything.

Also, a better optimization would be to pool. These lists may be very large. And they're going to be created, and then thrown away immediately when the DiffResult is created. If we pool them, then we only ever have as many lists created as needed to service the max number of threads doing diffing simultaneously.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done.

}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,9 @@ private void RemoveAllTags()

var snapshot = _subjectBuffer.CurrentSnapshot;
var oldTagTree = GetTagTree(snapshot, oldTagTrees);
var newTagTree = GetTagTree(snapshot, this.CachedTagTrees);

var difference = ComputeDifference(snapshot, newTagTree, oldTagTree);
RaiseTagsChanged(snapshot.TextBuffer, difference);

// everything from old tree is removed.
RaiseTagsChanged(snapshot.TextBuffer, new DiffResult(added: null, removed: oldTagTree.GetSpans(snapshot).Select(s => s.Span)));
}

private void OnSubjectBufferChanged(object sender, TextContentChangedEventArgs e)
Expand Down Expand Up @@ -193,11 +192,13 @@ private void RemoveTagsThatIntersectEdit(TextContentChangedEventArgs e)
var oldTagTrees = this.CachedTagTrees;
this.CachedTagTrees = oldTagTrees.SetItem(snapshot.TextBuffer, newTagTree);

// Grab our old tags. We might not have any, so in this case we'll just pretend it's
// empty
var oldTagTree = GetTagTree(snapshot, oldTagTrees);
// Not sure why we are diffing when we already have tagsToRemove. is it due to _tagSpanComparer might return
// different result than GetIntersectingSpans?
//
// treeForBuffer basically points to oldTagTrees. case where oldTagTrees not exist is already taken cared by
// CachedTagTrees.TryGetValue.
var difference = ComputeDifference(snapshot, newTagTree, treeForBuffer);
Copy link
Member

Choose a reason for hiding this comment

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

If you're not using oldTagTrees, then remove the local.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oh. good catch.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done


var difference = ComputeDifference(snapshot, newTagTree, oldTagTree);
RaiseTagsChanged(snapshot.TextBuffer, difference);
}

Expand Down Expand Up @@ -594,7 +595,7 @@ private void ProcessNewTagTrees(
object newState,
CancellationToken cancellationToken)
{
var bufferToChanges = new Dictionary<ITextBuffer, NormalizedSnapshotSpanCollection>();
var bufferToChanges = new Dictionary<ITextBuffer, DiffResult>();
using (Logger.LogBlock(FunctionId.Tagger_TagSource_ProcessNewTags, cancellationToken))
{
foreach (var latestBuffer in newTagTrees.Keys)
Expand All @@ -609,8 +610,7 @@ private void ProcessNewTagTrees(
else
{
// It's a new buffer, so report all spans are changed
var allSpans = new NormalizedSnapshotSpanCollection(newTagTrees[latestBuffer].GetSpans(snapshot).Select(t => t.Span));
bufferToChanges[latestBuffer] = allSpans;
bufferToChanges[latestBuffer] = new DiffResult(added: newTagTrees[latestBuffer].GetSpans(snapshot).Select(t => t.Span), removed: null);
}
}

Expand All @@ -619,8 +619,7 @@ private void ProcessNewTagTrees(
if (!newTagTrees.ContainsKey(oldBuffer))
{
// This buffer disappeared, so let's notify that the old tags are gone
var allSpans = new NormalizedSnapshotSpanCollection(oldTagTrees[oldBuffer].GetSpans(oldBuffer.CurrentSnapshot).Select(t => t.Span));
bufferToChanges[oldBuffer] = allSpans;
bufferToChanges[oldBuffer] = new DiffResult(added: null, removed: oldTagTrees[oldBuffer].GetSpans(oldBuffer.CurrentSnapshot).Select(t => t.Span));
}
}
}
Expand All @@ -640,7 +639,7 @@ private void ProcessNewTagTrees(

private void UpdateStateAndReportChanges(
ImmutableDictionary<ITextBuffer, TagSpanIntervalTree<TTag>> newTagTrees,
Dictionary<ITextBuffer, NormalizedSnapshotSpanCollection> bufferToChanges,
Dictionary<ITextBuffer, DiffResult> bufferToChanges,
object newState)
{
_workQueue.AssertIsForeground();
Expand Down Expand Up @@ -673,13 +672,12 @@ private void UpdateStateAndReportChanges(
RaiseTagsChanged(bufferToChanges);
}

private NormalizedSnapshotSpanCollection ComputeDifference(
private DiffResult ComputeDifference(
ITextSnapshot snapshot,
TagSpanIntervalTree<TTag> latestSpans,
TagSpanIntervalTree<TTag> previousSpans)
{
return new NormalizedSnapshotSpanCollection(
Difference(latestSpans.GetSpans(snapshot), previousSpans.GetSpans(snapshot), _dataSource.TagComparer));
return Difference(latestSpans.GetSpans(snapshot), previousSpans.GetSpans(snapshot), _dataSource.TagComparer);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.Editor.Shared.Tagging;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Tagging;
Expand Down Expand Up @@ -77,7 +78,7 @@ private void OnResumed(object sender, EventArgs e)
_batchChangeNotifier.Resume();
}

private void OnTagsChangedForBuffer(ICollection<KeyValuePair<ITextBuffer, NormalizedSnapshotSpanCollection>> changes)
private void OnTagsChangedForBuffer(ICollection<KeyValuePair<ITextBuffer, DiffResult>> changes)
{
_tagSource.AssertIsForeground();

Expand All @@ -94,10 +95,27 @@ private void OnTagsChangedForBuffer(ICollection<KeyValuePair<ITextBuffer, Normal
}

// Now report them back to the UI on the main thread.
_batchChangeNotifier.EnqueueChanges(change.Value);

// We ask to update UI immediately for removed tags
NotifyEditors(change.Value.Removed, _tagSource.OldTagNotificationDelay);
NotifyEditors(change.Value.Added, _tagSource.NewTagsNotificationDelay);
}
}

private void NotifyEditors(NormalizedSnapshotSpanCollection changes, TaggerDelay delay)
{
if (delay == TaggerDelay.NearImmediate)
Copy link
Member

Choose a reason for hiding this comment

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

Bail immediately if changes is empty.

Copy link
Member

Choose a reason for hiding this comment

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

Add foreground assertion.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

{
// if delay is immediate, we let notifier knows about the change right away
_batchChangeNotifier.EnqueueChanges(changes);
return;
}

// if delay is anything more than that, we let notifier knows about the change after given delay
// event notification is not cancellable.
_tagSource.RegisterNotification(() => _batchChangeNotifier.EnqueueChanges(changes), (int)delay.ComputeTimeDelay(_subjectBuffer).TotalMilliseconds, CancellationToken.None);
}

public IEnumerable<ITagSpan<TTag>> GetTags(NormalizedSnapshotSpanCollection requestedSpans)
{
return GetTagsWorker(requestedSpans, accurate: false, cancellationToken: CancellationToken.None);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,16 @@ internal abstract partial class AbstractAsynchronousTaggerProvider<TTag> : Foreg
protected virtual IEnumerable<Option<bool>> Options => SpecializedCollections.EmptyEnumerable<Option<bool>>();
protected virtual IEnumerable<PerLanguageOption<bool>> PerLanguageOptions => SpecializedCollections.EmptyEnumerable<PerLanguageOption<bool>>();

/// <summary>
/// This controls what delay tagger will use to let editor know about newly inserted tags
/// </summary>
protected virtual TaggerDelay NewTagsNotificationDelay => TaggerDelay.NearImmediate;

/// <summary>
/// This controls what delay tagger will use to let editor know about just deleted tags.
/// </summary>
protected virtual TaggerDelay OldTagNotificationDelay => TaggerDelay.NearImmediate;
Copy link
Member

Choose a reason for hiding this comment

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

OldTag is not the right name. This should be something like "RemovedTagNotificationDelay" vs "AddedTagNotificationDelay".

Copy link
Member

Choose a reason for hiding this comment

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

Should these both be NearImmediate by default? What does that do to existing taggers?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

NearImmediate is the existing behavior.


#if DEBUG
public readonly string StackTrace;
#endif
Expand Down Expand Up @@ -212,5 +222,19 @@ protected virtual Task ProduceTagsAsync(TaggerContext<TTag> context, DocumentSna
{
return SpecializedTasks.EmptyTask;
}

private struct DiffResult
{
public NormalizedSnapshotSpanCollection Added { get; }
public NormalizedSnapshotSpanCollection Removed { get; }

public DiffResult(IEnumerable<SnapshotSpan> added, IEnumerable<SnapshotSpan> removed)
{
Added = added != null ? new NormalizedSnapshotSpanCollection(added) : NormalizedSnapshotSpanCollection.Empty;
Removed = removed != null ? new NormalizedSnapshotSpanCollection(removed) : NormalizedSnapshotSpanCollection.Empty;
}

public int Count => Added.Count + Removed.Count;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ internal static class InternalSolutionCrawlerOptions
public const string OptionName = "SolutionCrawler";

public static readonly Option<bool> SolutionCrawler = new Option<bool>("FeatureManager/Components", "Solution Crawler", defaultValue: true);
public static readonly Option<int> ActiveFileWorkerBackOffTimeSpanInMS = new Option<int>(OptionName, "Active file worker backoff timespan in ms", defaultValue: 800);
public static readonly Option<int> ActiveFileWorkerBackOffTimeSpanInMS = new Option<int>(OptionName, "Active file worker backoff timespan in ms", defaultValue: 400);
public static readonly Option<int> AllFilesWorkerBackOffTimeSpanInMS = new Option<int>(OptionName, "All files worker backoff timespan in ms", defaultValue: 1500);
public static readonly Option<int> EntireProjectWorkerBackOffTimeSpanInMS = new Option<int>(OptionName, "Entire project analysis worker backoff timespan in ms", defaultValue: 5000);
public static readonly Option<int> SemanticChangeBackOffTimeSpanInMS = new Option<int>(OptionName, "Semantic change backoff timespan in ms", defaultValue: 100);
Expand Down