提交 dee2f952 编写于 作者: H Heejae Chang 提交者: GitHub

Merge pull request #16604 from heejaechang/fixperfRegression

removed additional cost I can find from trace compared to code before…
......@@ -13,7 +13,10 @@ namespace Microsoft.VisualStudio.LanguageServices
/// </summary>
internal sealed class SolutionEventMonitor : IDisposable
{
private GlobalOperationNotificationService _notificationService;
private const string SolutionBuilding = "Solution Building";
private const string SolutionOpening = "Solution Opening";
private IGlobalOperationNotificationService _notificationService;
private Dictionary<string, GlobalOperationRegistration> _operations = new Dictionary<string, GlobalOperationRegistration>();
public SolutionEventMonitor(VisualStudioWorkspace workspace)
......@@ -21,8 +24,23 @@ public SolutionEventMonitor(VisualStudioWorkspace workspace)
var notificationService = workspace.Services.GetService<IGlobalOperationNotificationService>() as GlobalOperationNotificationService;
if (notificationService != null)
{
// subscribe to events only if it is normal service. if it is one from unit test or other, don't bother to subscribe
_notificationService = notificationService;
// make sure we set initial state correctly. otherwise, we can get into a race where we might miss the very first events
if (KnownUIContexts.SolutionBuildingContext.IsActive)
{
ContextChanged(active: true, operation: SolutionBuilding);
}
KnownUIContexts.SolutionBuildingContext.UIContextChanged += SolutionBuildingContextChanged;
// make sure we set initial state correctly. otherwise, we can get into a race where we might miss the very first events
if (KnownUIContexts.SolutionOpeningContext.IsActive)
{
ContextChanged(active: true, operation: SolutionOpening);
}
KnownUIContexts.SolutionOpeningContext.UIContextChanged += SolutionOpeningContextChanged;
}
}
......@@ -46,15 +64,15 @@ public void Dispose()
private void SolutionBuildingContextChanged(object sender, UIContextChangedEventArgs e)
{
ContextChangedWorker(e, "Solution Building");
ContextChanged(e.Activated, SolutionBuilding);
}
private void SolutionOpeningContextChanged(object sender, UIContextChangedEventArgs e)
{
ContextChangedWorker(e, "Solution Opening");
ContextChanged(e.Activated, SolutionOpening);
}
private void ContextChangedWorker(UIContextChangedEventArgs e, string operation)
private void ContextChanged(bool active, string operation)
{
if (_notificationService == null)
{
......@@ -63,7 +81,7 @@ private void ContextChangedWorker(UIContextChangedEventArgs e, string operation)
TryCancelPendingNotification(operation);
if (e.Activated)
if (active)
{
_operations[operation] = _notificationService.Start(operation);
}
......@@ -75,6 +93,7 @@ private void TryCancelPendingNotification(string operation)
{
globalOperation.Done();
globalOperation.Dispose();
_operations.Remove(operation);
}
}
......
......@@ -9,6 +9,7 @@
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
using Microsoft.CodeAnalysis.Remote;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Roslyn.VisualStudio.Next.UnitTests.Mocks;
using Xunit;
......@@ -114,7 +115,7 @@ public async Task TestProjectSynchronization()
var source = new TestAssetSource(storage, sessionId, map);
var service = new AssetService(sessionId, storage);
await service.SynchronizeProjectAssetsAsync(await project.State.GetChecksumAsync(CancellationToken.None), CancellationToken.None);
await service.SynchronizeProjectAssetsAsync(SpecializedCollections.SingletonEnumerable(await project.State.GetChecksumAsync(CancellationToken.None)), CancellationToken.None);
object data;
foreach (var kv in map)
......
......@@ -86,7 +86,7 @@ public async Task SynchronizeSolutionAssetsAsync(Checksum solutionChecksum, Canc
}
}
public async Task SynchronizeProjectAssetsAsync(Checksum projectChecksum, CancellationToken cancellationToken)
public async Task SynchronizeProjectAssetsAsync(IEnumerable<Checksum> projectChecksums, CancellationToken cancellationToken)
{
// this will pull in assets that belong to the given project checksum to this remote host.
// this one is not supposed to be used for functionality but only for perf. that is why it doesn't return anything.
......@@ -96,10 +96,10 @@ public async Task SynchronizeProjectAssetsAsync(Checksum projectChecksum, Cancel
// one can call this method to make cache hot for all assets that belong to the project checksum so that GetAssetAsync call will most likely cache hit.
// it is most likely since we might change cache hueristic in future which make data to live a lot shorter in the cache, and the data might get expired
// before one actually consume the data.
using (Logger.LogBlock(FunctionId.AssetService_SynchronizeProjectAssetsAsync, Checksum.GetChecksumLogInfo, projectChecksum, cancellationToken))
using (Logger.LogBlock(FunctionId.AssetService_SynchronizeProjectAssetsAsync, Checksum.GetChecksumsLogInfo, projectChecksums, cancellationToken))
{
var syncer = new ChecksumSynchronizer(this);
await syncer.SynchronizeProjectAssetsAsync(projectChecksum, cancellationToken).ConfigureAwait(false);
await syncer.SynchronizeProjectAssetsAsync(projectChecksums, cancellationToken).ConfigureAwait(false);
}
}
......
......@@ -23,10 +23,8 @@ public ChecksumSynchronizer(AssetService assetService)
public async Task SynchronizeAssetsAsync(IEnumerable<Checksum> checksums, CancellationToken cancellationToken)
{
using (await s_gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false))
using (var pooledObject = SharedPools.Default<HashSet<Checksum>>().GetPooledObject())
{
AddIfNeeded(pooledObject.Object, checksums);
await _assetService.SynchronizeAssetsAsync(pooledObject.Object, cancellationToken).ConfigureAwait(false);
await SynchronizeAssets_NoLockAsync(checksums, cancellationToken).ConfigureAwait(false);
}
}
......@@ -40,100 +38,77 @@ public async Task SynchronizeSolutionAssetsAsync(Checksum solutionChecksum, Canc
var solutionChecksumObject = await _assetService.GetAssetAsync<SolutionStateChecksums>(solutionChecksum, cancellationToken).ConfigureAwait(false);
// second, get direct children of the solution
await SynchronizeSolutionAsync(solutionChecksumObject, cancellationToken).ConfigureAwait(false);
// third, get direct children for all projects in the solution
await SynchronizeProjectsAsync(solutionChecksumObject, cancellationToken).ConfigureAwait(false);
await SynchronizeAssets_NoLockAsync(solutionChecksumObject.Children, cancellationToken).ConfigureAwait(false);
// last, get direct children for all documents in the solution
await SynchronizeDocumentsAsync(solutionChecksumObject, cancellationToken).ConfigureAwait(false);
// third and last get direct children for all projects and documents in the solution
await SynchronizeProjectAssets_NoLockAsync(solutionChecksumObject.Projects, cancellationToken).ConfigureAwait(false);
}
}
public async Task SynchronizeProjectAssetsAsync(Checksum projectChecksum, CancellationToken cancellationToken)
public async Task SynchronizeProjectAssetsAsync(IEnumerable<Checksum> projectChecksums, CancellationToken cancellationToken)
{
using (await s_gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false))
{
await SynchronizeProjectAssets_NoLockAsync(projectChecksums, cancellationToken).ConfigureAwait(false);
}
}
private async Task SynchronizeProjectAssets_NoLockAsync(IEnumerable<Checksum> projectChecksums, CancellationToken cancellationToken)
{
// get children of project checksum objects at once
await SynchronizeProjectsAsync(projectChecksums, cancellationToken).ConfigureAwait(false);
// get children of document checksum objects at once
using (var pooledObject = SharedPools.Default<HashSet<Checksum>>().GetPooledObject())
{
var checksums = pooledObject.Object;
var projectChecksumObject = await _assetService.GetAssetAsync<ProjectStateChecksums>(projectChecksum, cancellationToken).ConfigureAwait(false);
AddIfNeeded(checksums, projectChecksumObject.Children);
foreach (var checksum in projectChecksumObject.Documents)
foreach (var projectChecksum in projectChecksums)
{
var documentChecksumObject = await _assetService.GetAssetAsync<DocumentStateChecksums>(checksum, cancellationToken).ConfigureAwait(false);
AddIfNeeded(checksums, documentChecksumObject.Children);
}
var projectChecksumObject = await _assetService.GetAssetAsync<ProjectStateChecksums>(projectChecksum, cancellationToken).ConfigureAwait(false);
foreach (var checksum in projectChecksumObject.AdditionalDocuments)
{
var documentChecksumObject = await _assetService.GetAssetAsync<DocumentStateChecksums>(checksum, cancellationToken).ConfigureAwait(false);
AddIfNeeded(checksums, documentChecksumObject.Children);
await CollectChecksumChildrenAsync(checksums, projectChecksumObject.Documents, cancellationToken).ConfigureAwait(false);
await CollectChecksumChildrenAsync(checksums, projectChecksumObject.AdditionalDocuments, cancellationToken).ConfigureAwait(false);
}
await _assetService.SynchronizeAssetsAsync(checksums, cancellationToken).ConfigureAwait(false);
}
}
private async Task SynchronizeSolutionAsync(SolutionStateChecksums solutionChecksumObject, CancellationToken cancellationToken)
private async Task SynchronizeProjectsAsync(IEnumerable<Checksum> projectChecksums, CancellationToken cancellationToken)
{
// get children of solution checksum object at once
// get children of project checksum objects at once
using (var pooledObject = SharedPools.Default<HashSet<Checksum>>().GetPooledObject())
{
var solutionChecksums = pooledObject.Object;
var checksums = pooledObject.Object;
AddIfNeeded(solutionChecksums, solutionChecksumObject.Children);
await _assetService.SynchronizeAssetsAsync(solutionChecksums, cancellationToken).ConfigureAwait(false);
await CollectChecksumChildrenAsync(checksums, projectChecksums, cancellationToken).ConfigureAwait(false);
await _assetService.SynchronizeAssetsAsync(checksums, cancellationToken).ConfigureAwait(false);
}
}
private async Task SynchronizeProjectsAsync(SolutionStateChecksums solutionChecksumObject, CancellationToken cancellationToken)
private async Task SynchronizeAssets_NoLockAsync(IEnumerable<object> checksumOrCollections, CancellationToken cancellationToken)
{
// get children of project checksum objects at once
// get children of solution checksum object at once
using (var pooledObject = SharedPools.Default<HashSet<Checksum>>().GetPooledObject())
{
var projectChecksums = pooledObject.Object;
foreach (var projectChecksum in solutionChecksumObject.Projects)
{
var projectChecksumObject = await _assetService.GetAssetAsync<ProjectStateChecksums>(projectChecksum, cancellationToken).ConfigureAwait(false);
AddIfNeeded(projectChecksums, projectChecksumObject.Children);
}
var checksums = pooledObject.Object;
await _assetService.SynchronizeAssetsAsync(projectChecksums, cancellationToken).ConfigureAwait(false);
AddIfNeeded(checksums, checksumOrCollections);
await _assetService.SynchronizeAssetsAsync(checksums, cancellationToken).ConfigureAwait(false);
}
}
private async Task SynchronizeDocumentsAsync(SolutionStateChecksums solutionChecksumObject, CancellationToken cancellationToken)
private async Task CollectChecksumChildrenAsync(HashSet<Checksum> set, IEnumerable<Checksum> checksums, CancellationToken cancellationToken)
{
// get children of document checksum objects at once
using (var pooledObject = SharedPools.Default<HashSet<Checksum>>().GetPooledObject())
foreach (var checksum in checksums)
{
var documentChecksums = pooledObject.Object;
foreach (var projectChecksum in solutionChecksumObject.Projects)
{
var projectChecksumObject = await _assetService.GetAssetAsync<ProjectStateChecksums>(projectChecksum, cancellationToken).ConfigureAwait(false);
foreach (var checksum in projectChecksumObject.Documents)
{
var documentChecksumObject = await _assetService.GetAssetAsync<DocumentStateChecksums>(checksum, cancellationToken).ConfigureAwait(false);
AddIfNeeded(documentChecksums, documentChecksumObject.Children);
}
foreach (var checksum in projectChecksumObject.AdditionalDocuments)
{
var documentChecksumObject = await _assetService.GetAssetAsync<DocumentStateChecksums>(checksum, cancellationToken).ConfigureAwait(false);
AddIfNeeded(documentChecksums, documentChecksumObject.Children);
}
}
await _assetService.SynchronizeAssetsAsync(documentChecksums, cancellationToken).ConfigureAwait(false);
var checksumObject = await _assetService.GetAssetAsync<ChecksumWithChildren>(checksum, cancellationToken).ConfigureAwait(false);
AddIfNeeded(set, checksumObject.Children);
}
}
private void AddIfNeeded(HashSet<Checksum> checksums, IReadOnlyList<object> checksumOrCollections)
private void AddIfNeeded(HashSet<Checksum> checksums, IEnumerable<object> checksumOrCollections)
{
foreach (var checksumOrCollection in checksumOrCollections)
{
......@@ -155,14 +130,6 @@ private void AddIfNeeded(HashSet<Checksum> checksums, IReadOnlyList<object> chec
}
}
private void AddIfNeeded(HashSet<Checksum> checksums, IEnumerable<Checksum> collection)
{
foreach (var checksum in collection)
{
AddIfNeeded(checksums, checksum);
}
}
private void AddIfNeeded(HashSet<Checksum> checksums, Checksum checksum)
{
if (!_assetService.EnsureCacheEntryIfExists(checksum))
......
......@@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Diagnostics;
......@@ -24,6 +25,8 @@ internal class SolutionCreator
public SolutionCreator(AssetService assetService, Solution baseSolution, CancellationToken cancellationToken)
{
Contract.ThrowIfNull(baseSolution);
_assetService = assetService;
_baseSolution = baseSolution;
_cancellationToken = cancellationToken;
......@@ -76,8 +79,10 @@ public async Task<Solution> CreateSolutionAsync(Checksum newSolutionChecksum)
solution = await UpdateProjectsAsync(solution, oldSolutionChecksums.Projects, newSolutionChecksums.Projects).ConfigureAwait(false);
}
#if DEBUG
// make sure created solution has same checksum as given one
Contract.ThrowIfFalse(newSolutionChecksum == await solution.State.GetChecksumAsync(_cancellationToken).ConfigureAwait(false));
Contract.Requires(newSolutionChecksum == await solution.State.GetChecksumAsync(_cancellationToken).ConfigureAwait(false));
#endif
return solution;
}
......@@ -103,14 +108,14 @@ private async Task<Solution> UpdateProjectsAsync(Solution solution, HashSet<Chec
var oldMap = await GetProjectMapAsync(solution, oldChecksums).ConfigureAwait(false);
var newMap = await GetProjectMapAsync(_assetService, newChecksums).ConfigureAwait(false);
// bulk sync assets
await SynchronizeAssetsAsync(solution, oldMap, newMap).ConfigureAwait(false);
// added project
foreach (var kv in newMap)
{
if (!oldMap.ContainsKey(kv.Key))
{
// bulk sync project assets
await _assetService.SynchronizeProjectAssetsAsync(kv.Value.Checksum, _cancellationToken).ConfigureAwait(false);
var projectInfo = await CreateProjectInfoAsync(kv.Value.Checksum).ConfigureAwait(false);
if (projectInfo == null)
{
......@@ -151,6 +156,25 @@ private async Task<Solution> UpdateProjectsAsync(Solution solution, HashSet<Chec
return solution;
}
private async Task SynchronizeAssetsAsync(Solution solution, Dictionary<ProjectId, ProjectStateChecksums> oldMap, Dictionary<ProjectId, ProjectStateChecksums> newMap)
{
using (var pooledObject = SharedPools.Default<HashSet<Checksum>>().GetPooledObject())
{
// added project
foreach (var kv in newMap)
{
if (oldMap.ContainsKey(kv.Key))
{
continue;
}
pooledObject.Object.Add(kv.Value.Checksum);
}
await _assetService.SynchronizeProjectAssetsAsync(pooledObject.Object, _cancellationToken).ConfigureAwait(false);
}
}
private async Task<Solution> UpdateProjectAsync(Project project, ProjectStateChecksums oldProjectChecksums, ProjectStateChecksums newProjectChecksums)
{
// changed info
......@@ -266,7 +290,7 @@ private async Task<Project> UpdateDocumentsAsync(Project project, HashSet<Checks
var oldMap = await GetDocumentMapAsync(project, oldChecksums, additionalText).ConfigureAwait(false);
var newMap = await GetDocumentMapAsync(_assetService, newChecksums).ConfigureAwait(false);
// added project
// added document
foreach (var kv in newMap)
{
if (!oldMap.ContainsKey(kv.Key))
......@@ -276,7 +300,7 @@ private async Task<Project> UpdateDocumentsAsync(Project project, HashSet<Checks
}
}
// changed project
// changed document
foreach (var kv in newMap)
{
DocumentStateChecksums oldDocumentChecksums;
......@@ -292,7 +316,7 @@ private async Task<Project> UpdateDocumentsAsync(Project project, HashSet<Checks
project = await UpdateDocumentAsync(document, oldDocumentChecksums, newDocumentChecksums, additionalText).ConfigureAwait(false);
}
// removed project
// removed document
foreach (var kv in oldMap)
{
if (!newMap.ContainsKey(kv.Key))
......@@ -370,6 +394,8 @@ private async Task<TextDocument> UpdateDocumentInfoAsync(TextDocument document,
var map = new Dictionary<DocumentId, DocumentStateChecksums>();
var documentChecksums = await assetService.GetAssetsAsync<DocumentStateChecksums>(documents, _cancellationToken).ConfigureAwait(false);
var infos = await assetService.GetAssetsAsync<DocumentInfo.DocumentAttributes>(documentChecksums.Select(p => p.Item2.Info), _cancellationToken).ConfigureAwait(false);
foreach (var kv in documentChecksums)
{
var info = await assetService.GetAssetAsync<DocumentInfo.DocumentAttributes>(kv.Item2.Info, _cancellationToken).ConfigureAwait(false);
......@@ -411,6 +437,8 @@ private async Task<TextDocument> UpdateDocumentInfoAsync(TextDocument document,
var map = new Dictionary<ProjectId, ProjectStateChecksums>();
var projectChecksums = await assetService.GetAssetsAsync<ProjectStateChecksums>(projects, _cancellationToken).ConfigureAwait(false);
var infos = await assetService.GetAssetsAsync<ProjectInfo.ProjectAttributes>(projectChecksums.Select(p => p.Item2.Info), _cancellationToken).ConfigureAwait(false);
foreach (var kv in projectChecksums)
{
var info = await assetService.GetAssetAsync<ProjectInfo.ProjectAttributes>(kv.Item2.Info, _cancellationToken).ConfigureAwait(false);
......
......@@ -20,8 +20,8 @@ internal class SolutionService
private readonly AssetService _assetService;
// TODO: make this simple cache better
// this simple cache hold onto the last solution created
// this simple cache hold onto the last and primary solution created
private volatile static Tuple<Checksum, Solution> s_primarySolution;
private volatile static Tuple<Checksum, Solution> s_lastSolution;
public SolutionService(AssetService assetService)
......@@ -31,30 +31,22 @@ public SolutionService(AssetService assetService)
public async Task<Solution> GetSolutionAsync(Checksum solutionChecksum, CancellationToken cancellationToken)
{
var currentSolution = s_primaryWorkspace.CurrentSolution;
var primarySolutionChecksum = await currentSolution.State.GetChecksumAsync(cancellationToken).ConfigureAwait(false);
if (primarySolutionChecksum == solutionChecksum)
var currentSolution = GetAvailableSolution(solutionChecksum);
if (currentSolution != null)
{
// nothing changed
return currentSolution;
}
var lastSolution = s_lastSolution;
if (lastSolution?.Item1 == solutionChecksum)
{
return lastSolution.Item2;
}
// make sure there is always only one that creates a new solution
using (await s_gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false))
{
if (s_lastSolution?.Item1 == solutionChecksum)
currentSolution = GetAvailableSolution(solutionChecksum);
if (currentSolution != null)
{
return s_lastSolution.Item2;
return currentSolution;
}
var solution = await CreateSolution_NoLockAsync(solutionChecksum, currentSolution, cancellationToken).ConfigureAwait(false);
var solution = await CreateSolution_NoLockAsync(solutionChecksum, s_primaryWorkspace.CurrentSolution, cancellationToken).ConfigureAwait(false);
s_lastSolution = Tuple.Create(solutionChecksum, solution);
return solution;
......@@ -63,14 +55,13 @@ public async Task<Solution> GetSolutionAsync(Checksum solutionChecksum, Cancella
public async Task<Solution> GetSolutionAsync(Checksum solutionChecksum, OptionSet optionSet, CancellationToken cancellationToken)
{
// get solution
var baseSolution = await GetSolutionAsync(solutionChecksum, cancellationToken).ConfigureAwait(false);
if (optionSet == null)
{
return await GetSolutionAsync(solutionChecksum, cancellationToken).ConfigureAwait(false);
return baseSolution;
}
// get solution
var baseSolution = await GetSolutionAsync(solutionChecksum, cancellationToken).ConfigureAwait(false);
// since options belong to workspace, we can't share solution
// create temporary workspace
var tempWorkspace = new TemporaryWorkspace(baseSolution);
......@@ -95,20 +86,8 @@ public async Task UpdatePrimaryWorkspaceAsync(Checksum solutionChecksum, Cancell
using (await s_gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false))
{
var updater = new SolutionCreator(_assetService, currentSolution, cancellationToken);
if (await updater.IsIncrementalUpdateAsync(solutionChecksum).ConfigureAwait(false))
{
// solution has updated
s_primaryWorkspace.UpdateSolution(await updater.CreateSolutionAsync(solutionChecksum).ConfigureAwait(false));
return;
}
// new solution. bulk sync all asset for the solution
await _assetService.SynchronizeSolutionAssetsAsync(solutionChecksum, cancellationToken).ConfigureAwait(false);
s_primaryWorkspace.ClearSolution();
s_primaryWorkspace.AddSolution(await updater.CreateSolutionInfoAsync(solutionChecksum).ConfigureAwait(false));
var solution = await UpdatePrimaryWorkspace_NoLockAsync(solutionChecksum, currentSolution, cancellationToken).ConfigureAwait(false);
s_primarySolution = Tuple.Create(solutionChecksum, solution);
}
}
......@@ -139,5 +118,45 @@ private async Task<Solution> CreateSolution_NoLockAsync(Checksum solutionChecksu
var workspace = new TemporaryWorkspace(await updater.CreateSolutionInfoAsync(solutionChecksum).ConfigureAwait(false));
return workspace.CurrentSolution;
}
private async Task<Solution> UpdatePrimaryWorkspace_NoLockAsync(Checksum solutionChecksum, Solution baseSolution, CancellationToken cancellationToken)
{
var updater = new SolutionCreator(_assetService, baseSolution, cancellationToken);
if (await updater.IsIncrementalUpdateAsync(solutionChecksum).ConfigureAwait(false))
{
// solution has updated
s_primaryWorkspace.UpdateSolution(await updater.CreateSolutionAsync(solutionChecksum).ConfigureAwait(false));
return s_primaryWorkspace.CurrentSolution;
}
// new solution. bulk sync all asset for the solution
await _assetService.SynchronizeSolutionAssetsAsync(solutionChecksum, cancellationToken).ConfigureAwait(false);
s_primaryWorkspace.ClearSolution();
s_primaryWorkspace.AddSolution(await updater.CreateSolutionInfoAsync(solutionChecksum).ConfigureAwait(false));
return s_primaryWorkspace.CurrentSolution;
}
private static Solution GetAvailableSolution(Checksum solutionChecksum)
{
var currentSolution = s_primarySolution;
if (currentSolution?.Item1 == solutionChecksum)
{
// asked about primary solution
return currentSolution.Item2;
}
var lastSolution = s_lastSolution;
if (lastSolution?.Item1 == solutionChecksum)
{
// asked about last solution
return lastSolution.Item2;
}
return null;
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册