Skip to content

Commit

Permalink
Use checksums for Roslyn OOP communication
Browse files Browse the repository at this point in the history
This change updates TagHelperDeltaResult to return Checksums for
tag helpers that were added and introduces a new OOP API for fetching
tag heleprs by checksum. These are used to only fetch tag helpers from
Roslyn OOP that aren't already cached by Razor tooling.
  • Loading branch information
DustinCampbell committed Sep 5, 2023
1 parent 97421c1 commit 5b07c22
Show file tree
Hide file tree
Showing 24 changed files with 611 additions and 189 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
using System.Linq;
using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Utilities;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Remote.Razor;
using Checksum = Microsoft.AspNetCore.Razor.Utilities.Checksum;

namespace Microsoft.AspNetCore.Razor.Microbenchmarks;

Expand Down Expand Up @@ -38,17 +40,22 @@ public RemoteTagHelperDeltaProviderBenchmark()
.ToHashSet()
.ToImmutableArray();

DefaultTagHelperChecksumsSet = DefaultTagHelperSet.SelectAsArray(t => t.GetChecksum());
Added50PercentMoreDefaultTagHelpersChecksums = Added50PercentMoreDefaultTagHelpers.SelectAsArray(t => t.GetChecksum());
RemovedHalfOfDefaultTagHelpersChecksums = RemovedHalfOfDefaultTagHelpers.SelectAsArray(t => t.GetChecksum());
MutatedTwoDefaultTagHelpersChecksums = MutatedTwoDefaultTagHelpers.SelectAsArray(t => t.GetChecksum());

ProjectId = ProjectId.CreateNewId();
}

private ImmutableArray<TagHelperDescriptor> DefaultTagHelperSet { get; }

private ImmutableArray<Checksum> DefaultTagHelperChecksumsSet { get; }
private ImmutableArray<TagHelperDescriptor> Added50PercentMoreDefaultTagHelpers { get; }

private ImmutableArray<Checksum> Added50PercentMoreDefaultTagHelpersChecksums { get; }
private ImmutableArray<TagHelperDescriptor> RemovedHalfOfDefaultTagHelpers { get; }

private ImmutableArray<Checksum> RemovedHalfOfDefaultTagHelpersChecksums { get; }
private ImmutableArray<TagHelperDescriptor> MutatedTwoDefaultTagHelpers { get; }

private ImmutableArray<Checksum> MutatedTwoDefaultTagHelpersChecksums { get; }
private ProjectId ProjectId { get; }

[AllowNull]
Expand All @@ -60,45 +67,45 @@ public RemoteTagHelperDeltaProviderBenchmark()
public void IterationSetup()
{
Provider = new RemoteTagHelperDeltaProvider();
var delta = Provider.GetTagHelpersDelta(ProjectId, lastResultId: -1, DefaultTagHelperSet);
var delta = Provider.GetTagHelpersDelta(ProjectId, lastResultId: -1, DefaultTagHelperChecksumsSet);
LastResultId = delta.ResultId;
}

[Benchmark(Description = "Calculate Delta - New project")]
public void TagHelper_GetTagHelpersDelta_NewProject()
{
var projectId = ProjectId.CreateNewId();
_ = Provider.GetTagHelpersDelta(projectId, lastResultId: -1, DefaultTagHelperSet);
_ = Provider.GetTagHelpersDelta(projectId, lastResultId: -1, DefaultTagHelperChecksumsSet);
}

[Benchmark(Description = "Calculate Delta - Remove project")]
public void TagHelper_GetTagHelpersDelta_RemoveProject()
{
_ = Provider.GetTagHelpersDelta(ProjectId, LastResultId, ImmutableArray<TagHelperDescriptor>.Empty);
_ = Provider.GetTagHelpersDelta(ProjectId, LastResultId, ImmutableArray<Checksum>.Empty);
}

[Benchmark(Description = "Calculate Delta - Add lots of TagHelpers")]
public void TagHelper_GetTagHelpersDelta_AddLots()
{
_ = Provider.GetTagHelpersDelta(ProjectId, LastResultId, Added50PercentMoreDefaultTagHelpers);
_ = Provider.GetTagHelpersDelta(ProjectId, LastResultId, Added50PercentMoreDefaultTagHelpersChecksums);
}

[Benchmark(Description = "Calculate Delta - Remove lots of TagHelpers")]
public void TagHelper_GetTagHelpersDelta_RemoveLots()
{
_ = Provider.GetTagHelpersDelta(ProjectId, LastResultId, RemovedHalfOfDefaultTagHelpers);
_ = Provider.GetTagHelpersDelta(ProjectId, LastResultId, RemovedHalfOfDefaultTagHelpersChecksums);
}

[Benchmark(Description = "Calculate Delta - Mutate two TagHelpers")]
public void TagHelper_GetTagHelpersDelta_Mutate2()
{
_ = Provider.GetTagHelpersDelta(ProjectId, LastResultId, MutatedTwoDefaultTagHelpers);
_ = Provider.GetTagHelpersDelta(ProjectId, LastResultId, MutatedTwoDefaultTagHelpersChecksums);
}

[Benchmark(Description = "Calculate Delta - No change")]
public void TagHelper_GetTagHelpersDelta_NoChange()
{
_ = Provider.GetTagHelpersDelta(ProjectId, LastResultId, DefaultTagHelperSet);
_ = Provider.GetTagHelpersDelta(ProjectId, LastResultId, DefaultTagHelperChecksumsSet);
}

internal class RenamedTagHelperDescriptor : DefaultTagHelperDescriptor
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.

using System.Collections.Immutable;
using Microsoft.AspNetCore.Razor.Language;

namespace Microsoft.AspNetCore.Razor.Serialization;

internal sealed record FetchTagHelpersResult(ImmutableArray<TagHelperDescriptor> TagHelpers)
{
public static readonly FetchTagHelpersResult Empty = new(ImmutableArray<TagHelperDescriptor>.Empty);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ protected override TagHelperDeltaResult ReadFromProperties(JsonDataReader reader
{
var delta = reader.ReadBooleanOrTrue(nameof(TagHelperDeltaResult.Delta));
var resultId = reader.ReadInt32OrZero(nameof(TagHelperDeltaResult.ResultId));
var added = reader.ReadImmutableArrayOrEmpty(nameof(TagHelperDeltaResult.Added),
static r => ObjectReaders.ReadTagHelper(r, useCache: true));
var added = reader.ReadImmutableArrayOrEmpty(nameof(TagHelperDeltaResult.Added), ObjectReaders.ReadChecksum);
var removed = reader.ReadImmutableArrayOrEmpty(nameof(TagHelperDeltaResult.Removed), ObjectReaders.ReadChecksum);

return new(delta, resultId, added, removed);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ public static TagHelperDescriptor ReadTagHelper(JsonDataReader reader, bool useC

public static TagHelperDescriptor ReadTagHelperFromProperties(JsonDataReader reader, bool useCache)
{
TagHelperDescriptor? descriptor;
TagHelperDescriptor? tagHelper;
var checksumWasRead = false;

// Try reading the optional checksum
Expand All @@ -123,10 +123,10 @@ public static TagHelperDescriptor ReadTagHelperFromProperties(JsonDataReader rea
var checksum = ReadChecksum(reader);
checksumWasRead = true;

if (useCache && TagHelperDescriptorCache.TryGetDescriptor(checksum, out descriptor))
if (useCache && TagHelperCache.Default.TryGet(checksum, out tagHelper))
{
reader.ReadToEndOfCurrentObject();
return descriptor;
return tagHelper;
}
}

Expand All @@ -146,7 +146,7 @@ public static TagHelperDescriptor ReadTagHelperFromProperties(JsonDataReader rea
var metadata = ReadMetadata(reader, nameof(TagHelperDescriptor.Metadata));
var diagnostics = reader.ReadArrayOrEmpty(nameof(TagHelperDescriptor.Diagnostics), ReadDiagnostic);

descriptor = new DefaultTagHelperDescriptor(
tagHelper = new DefaultTagHelperDescriptor(
Cached(kind), Cached(name), Cached(assemblyName),
Cached(displayName)!, documentationObject,
Cached(tagOutputHint), caseSensitive,
Expand All @@ -155,10 +155,10 @@ public static TagHelperDescriptor ReadTagHelperFromProperties(JsonDataReader rea

if (useCache && checksumWasRead)
{
TagHelperDescriptorCache.Set(descriptor.GetChecksum(), descriptor);
TagHelperCache.Default.TryAdd(tagHelper.GetChecksum(), tagHelper);
}

return descriptor;
return tagHelper;

static TagMatchingRuleDescriptor ReadTagMatchingRule(JsonDataReader reader)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.

using System.Collections.Immutable;
using MessagePack;
using Microsoft.AspNetCore.Razor.Language;

namespace Microsoft.AspNetCore.Razor.Serialization.MessagePack.Formatters;

internal sealed class FetchTagHelpersResultFormatter : TopLevelFormatter<FetchTagHelpersResult>
{
public static readonly TopLevelFormatter<FetchTagHelpersResult> Instance = new FetchTagHelpersResultFormatter();

private FetchTagHelpersResultFormatter()
{
}

protected override FetchTagHelpersResult Deserialize(ref MessagePackReader reader, SerializerCachingOptions options)
{
var tagHelpers = reader.Deserialize<ImmutableArray<TagHelperDescriptor>>(options);

return new(tagHelpers);
}

protected override void Serialize(ref MessagePackWriter writer, FetchTagHelpersResult value, SerializerCachingOptions options)
{
writer.SerializeObject(value.TagHelpers, options);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@

using System.Collections.Immutable;
using MessagePack;
using Microsoft.AspNetCore.Razor.Language;
using Checksum = Microsoft.AspNetCore.Razor.Utilities.Checksum;
using Microsoft.AspNetCore.Razor.Utilities;

namespace Microsoft.AspNetCore.Razor.Serialization.MessagePack.Formatters;

Expand All @@ -22,7 +21,7 @@ protected override TagHelperDeltaResult Deserialize(ref MessagePackReader reader

var delta = reader.ReadBoolean();
var resultId = reader.ReadInt32();
var added = reader.Deserialize<ImmutableArray<TagHelperDescriptor>>(options);
var added = reader.Deserialize<ImmutableArray<Checksum>>(options);
var removed = reader.Deserialize<ImmutableArray<Checksum>>(options);

return new(delta, resultId, added, removed);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using MessagePack;
using MessagePack.Formatters;
using Microsoft.AspNetCore.Razor.Serialization.MessagePack.Formatters;
using Microsoft.AspNetCore.Razor.Serialization.MessagePack.Formatters.TagHelpers;

namespace Microsoft.AspNetCore.Razor.Serialization.MessagePack.Resolvers;

internal sealed class FetchTagHelpersResultResolver : IFormatterResolver
{
public static readonly FetchTagHelpersResultResolver Instance = new();

private FetchTagHelpersResultResolver()
{
}

public IMessagePackFormatter<T>? GetFormatter<T>()
{
return Cache<T>.Formatter;
}

private static class Cache<T>
{
public static readonly IMessagePackFormatter<T>? Formatter;

static Cache()
{
Formatter = (IMessagePackFormatter<T>?)TypeToFormatterMap.GetFormatter(typeof(T));
}
}

private static class TypeToFormatterMap
{
private static readonly Dictionary<Type, object> s_map = new()
{
FetchTagHelpersResultFormatter.Instance,

// tag helpers
AllowedChildTagFormatter.Instance,
BoundAttributeFormatter.Instance,
BoundAttributeParameterFormatter.Instance,
DocumentationObjectFormatter.Instance,
MetadataCollectionFormatter.Instance,
RazorDiagnosticFormatter.Instance,
RequiredAttributeFormatter.Instance,
TagHelperFormatter.Instance,
TagMatchingRuleFormatter.Instance
};

public static object? GetFormatter(Type t)
{
if (s_map.TryGetValue(t, out var formatter))
{
return formatter;
}

return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ internal static class TopLevelResolvers
{
public static readonly ImmutableArray<IFormatterResolver> All = ImmutableArray.Create<IFormatterResolver>(
ChecksumResolver.Instance,
FetchTagHelpersResultResolver.Instance,
ProjectSnapshotHandleResolver.Instance,
RazorProjectInfoResolver.Instance,
TagHelperDeltaResultResolver.Instance);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,11 @@

using System.Collections.Immutable;
using System.Linq;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.PooledObjects;
using Microsoft.AspNetCore.Razor.Utilities;
using Microsoft.Extensions.Internal;
using Checksum = Microsoft.AspNetCore.Razor.Utilities.Checksum;

#if DEBUG
using System.Collections.Generic;
using System.Diagnostics;
#endif

Expand All @@ -19,30 +16,26 @@ namespace Microsoft.AspNetCore.Razor.Serialization;
internal sealed record TagHelperDeltaResult(
bool Delta,
int ResultId,
ImmutableArray<TagHelperDescriptor> Added,
ImmutableArray<Checksum> Added,
ImmutableArray<Checksum> Removed)
{
public ImmutableArray<TagHelperDescriptor> Apply(ImmutableArray<TagHelperDescriptor> baseTagHelpers)
public ImmutableArray<Checksum> Apply(ImmutableArray<Checksum> baseChecksums)
{
if (Added.Length == 0 && Removed.Length == 0)
{
return baseTagHelpers;
return baseChecksums;
}

// We're specifically choosing to create a List here instead of an alternate type like HashSet because
// results that are produced from `Apply` are typically fed back into two different systems:
//
// 1. This TagHelperDeltaResult.Apply where we don't iterate / Contains check the "base" collection.
// 2. The rest of the Razor project system. Everything there is always indexed / iterated as a list.
using var _ = ArrayBuilderPool<TagHelperDescriptor>.GetPooledObject(out var result);
result.SetCapacityIfLarger(baseTagHelpers.Length + Added.Length - Removed.Length);
using var _ = ArrayBuilderPool<Checksum>.GetPooledObject(out var result);
result.SetCapacityIfLarger(baseChecksums.Length + Added.Length - Removed.Length);

result.AddRange(Added);
result.AddRange(TagHelperDelta.Compute(Removed, baseTagHelpers));
result.AddRange(TagHelperDelta.Compute(Removed, baseChecksums));

#if DEBUG
// Ensure that there are no duplicate tag helpers in the result.
var set = new HashSet<TagHelperDescriptor>(TagHelperChecksumComparer.Instance);
using var pooledSet = HashSetPool<Checksum>.GetPooledObject();
var set = pooledSet.Object;

foreach (var item in result)
{
Expand All @@ -62,7 +55,7 @@ public bool Equals(TagHelperDeltaResult? other)

return Delta == other.Delta &&
ResultId == other.ResultId &&
Added.SequenceEqual(other.Added, TagHelperChecksumComparer.Instance) &&
Added.SequenceEqual(other.Added) &&
Removed.SequenceEqual(other.Removed);
}

Expand All @@ -72,7 +65,7 @@ public override int GetHashCode()

hash.Add(Delta);
hash.Add(ResultId);
hash.Add(Added, TagHelperChecksumComparer.Instance);
hash.Add(Added);
hash.Add(Removed);

return hash.CombinedHash;
Expand Down
Loading

0 comments on commit 5b07c22

Please sign in to comment.