Skip to content

Commit

Permalink
Merge pull request #48896 from sharwell/reduce-allocs
Browse files Browse the repository at this point in the history
Reduce allocation from data taken in local performance trace
  • Loading branch information
sharwell authored Oct 28, 2020
2 parents b1d8852 + 9609d50 commit 9f079e2
Show file tree
Hide file tree
Showing 41 changed files with 800 additions and 366 deletions.
66 changes: 66 additions & 0 deletions src/Compilers/Core/Portable/CaseInsensitiveComparison.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,24 @@ public override int Compare(string? str1, string? str2)
return str1.Length - str2.Length;
}

#if !NET20 && !NETSTANDARD1_3
public int Compare(ReadOnlySpan<char> str1, ReadOnlySpan<char> str2)
{
int len = Math.Min(str1.Length, str2.Length);
for (int i = 0; i < len; i++)
{
int ordDiff = CompareLowerUnicode(str1[i], str2[i]);
if (ordDiff != 0)
{
return ordDiff;
}
}

// return the smaller string, or 0 if they are equal in length
return str1.Length - str2.Length;
}
#endif

private static bool AreEqualLowerUnicode(char c1, char c2)
{
return c1 == c2 || ToLower(c1) == ToLower(c2);
Expand Down Expand Up @@ -150,6 +168,26 @@ public override bool Equals(string? str1, string? str2)
return true;
}

#if !NET20 && !NETSTANDARD1_3
public bool Equals(ReadOnlySpan<char> str1, ReadOnlySpan<char> str2)
{
if (str1.Length != str2.Length)
{
return false;
}

for (int i = 0; i < str1.Length; i++)
{
if (!AreEqualLowerUnicode(str1[i], str2[i]))
{
return false;
}
}

return true;
}
#endif

public static bool EndsWith(string value, string possibleEnd)
{
if ((object)value == possibleEnd)
Expand Down Expand Up @@ -255,6 +293,20 @@ public override int GetHashCode(string str)
/// </remarks>
public static bool Equals(string left, string right) => s_comparer.Equals(left, right);

#if !NET20 && !NETSTANDARD1_3
/// <summary>
/// Determines if two strings are equal according to Unicode rules for case-insensitive
/// identifier comparison (lower-case mapping).
/// </summary>
/// <param name="left">First identifier to compare</param>
/// <param name="right">Second identifier to compare</param>
/// <returns>true if the identifiers should be considered the same.</returns>
/// <remarks>
/// These are also the rules used for VB identifier comparison.
/// </remarks>
public static bool Equals(ReadOnlySpan<char> left, ReadOnlySpan<char> right) => s_comparer.Equals(left, right);
#endif

/// <summary>
/// Determines if the string 'value' end with string 'possibleEnd'.
/// </summary>
Expand Down Expand Up @@ -283,6 +335,20 @@ public override int GetHashCode(string str)
/// </remarks>
public static int Compare(string left, string right) => s_comparer.Compare(left, right);

#if !NET20 && !NETSTANDARD1_3
/// <summary>
/// Compares two strings according to the Unicode rules for case-insensitive
/// identifier comparison (lower-case mapping).
/// </summary>
/// <param name="left">First identifier to compare</param>
/// <param name="right">Second identifier to compare</param>
/// <returns>-1 if <paramref name="left"/> &lt; <paramref name="right"/>, 1 if <paramref name="left"/> &gt; <paramref name="right"/>, 0 if they are equal.</returns>
/// <remarks>
/// These are also the rules used for VB identifier comparison.
/// </remarks>
public static int Compare(ReadOnlySpan<char> left, ReadOnlySpan<char> right) => s_comparer.Compare(left, right);
#endif

/// <summary>
/// Gets a case-insensitive hash code for Unicode identifiers.
/// </summary>
Expand Down
9 changes: 4 additions & 5 deletions src/Compilers/Core/Portable/InternalUtilities/Hash.cs
Original file line number Diff line number Diff line change
Expand Up @@ -254,17 +254,16 @@ internal static int GetFNVHashCode(string text, int start, int length)

internal static int GetCaseInsensitiveFNVHashCode(string text)
{
return GetCaseInsensitiveFNVHashCode(text, 0, text.Length);
return GetCaseInsensitiveFNVHashCode(text.AsSpan(0, text.Length));
}

internal static int GetCaseInsensitiveFNVHashCode(string text, int start, int length)
internal static int GetCaseInsensitiveFNVHashCode(ReadOnlySpan<char> data)
{
int hashCode = Hash.FnvOffsetBias;
int end = start + length;

for (int i = start; i < end; i++)
for (int i = 0; i < data.Length; i++)
{
hashCode = unchecked((hashCode ^ CaseInsensitiveComparison.ToLower(text[i])) * Hash.FnvPrime);
hashCode = unchecked((hashCode ^ CaseInsensitiveComparison.ToLower(data[i])) * Hash.FnvPrime);
}

return hashCode;
Expand Down
2 changes: 2 additions & 0 deletions src/Compilers/Core/Portable/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
Microsoft.CodeAnalysis.ITypeSymbol.IsRecord.get -> bool
Microsoft.CodeAnalysis.SymbolDisplayPartKind.RecordName = 31 -> Microsoft.CodeAnalysis.SymbolDisplayPartKind
const Microsoft.CodeAnalysis.WellKnownDiagnosticTags.CompilationEnd = "CompilationEnd" -> string
static Microsoft.CodeAnalysis.CaseInsensitiveComparison.Compare(System.ReadOnlySpan<char> left, System.ReadOnlySpan<char> right) -> int
static Microsoft.CodeAnalysis.CaseInsensitiveComparison.Equals(System.ReadOnlySpan<char> left, System.ReadOnlySpan<char> right) -> bool
25 changes: 25 additions & 0 deletions src/Dependencies/PooledObjects/ArrayBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,29 @@ public ImmutableArray<T> ToImmutable()
return _builder.ToImmutable();
}

/// <summary>
/// Realizes the array and clears the collection.
/// </summary>
public ImmutableArray<T> ToImmutableAndClear()
{
ImmutableArray<T> result;
if (Count == 0)
{
result = ImmutableArray<T>.Empty;
}
else if (_builder.Capacity == Count)
{
result = _builder.MoveToImmutable();
}
else
{
result = ToImmutable();
Clear();
}

return result;
}

public int Count
{
get
Expand Down Expand Up @@ -281,6 +304,8 @@ public ImmutableArray<U> ToDowncastedImmutable<U>()
/// </summary>
public ImmutableArray<T> ToImmutableAndFree()
{
// This is mostly the same as 'MoveToImmutable', but avoids delegating to that method since 'Free' contains
// fast paths to avoid caling 'Clear' in some cases.
ImmutableArray<T> result;
if (Count == 0)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

namespace Microsoft.CodeAnalysis.FindSymbols
{
using ProjectToDocumentMap = Dictionary<Project, MultiDictionary<Document, (ISymbol symbol, IReferenceFinder finder)>>;
using ProjectToDocumentMap = Dictionary<Project, Dictionary<Document, HashSet<(ISymbol symbol, IReferenceFinder finder)>>>;

internal partial class FindReferencesSearchEngine
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,27 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#nullable disable

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.FindSymbols.Finders;
using Microsoft.CodeAnalysis.Internal.Log;
using Roslyn.Utilities;
using Microsoft.CodeAnalysis.Shared.Extensions;

namespace Microsoft.CodeAnalysis.FindSymbols
{
using DocumentMap = MultiDictionary<Document, (ISymbol symbol, IReferenceFinder finder)>;

internal partial class FindReferencesSearchEngine
{
private async Task ProcessDocumentQueueAsync(
Document document,
DocumentMap.ValueSet documentQueue)
HashSet<(ISymbol symbol, IReferenceFinder finder)> documentQueue)
{
await _progress.OnFindInDocumentStartedAsync(document).ConfigureAwait(false);

SemanticModel model = null;
SemanticModel? model = null;
try
{
model = await document.GetSemanticModelAsync(_cancellationToken).ConfigureAwait(false);
model = await document.GetRequiredSemanticModelAsync(_cancellationToken).ConfigureAwait(false);

// start cache for this semantic model
FindReferenceCache.Start(model);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#nullable disable

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
Expand All @@ -18,9 +16,9 @@

namespace Microsoft.CodeAnalysis.FindSymbols
{
using DocumentMap = MultiDictionary<Document, (ISymbol symbol, IReferenceFinder finder)>;
using ProjectMap = MultiDictionary<Project, (ISymbol symbol, IReferenceFinder finder)>;
using ProjectToDocumentMap = Dictionary<Project, MultiDictionary<Document, (ISymbol symbol, IReferenceFinder finder)>>;
using DocumentMap = Dictionary<Document, HashSet<(ISymbol symbol, IReferenceFinder finder)>>;
using ProjectMap = Dictionary<Project, HashSet<(ISymbol symbol, IReferenceFinder finder)>>;
using ProjectToDocumentMap = Dictionary<Project, Dictionary<Document, HashSet<(ISymbol symbol, IReferenceFinder finder)>>>;

internal partial class FindReferencesSearchEngine
{
Expand Down Expand Up @@ -49,7 +47,7 @@ private async Task<ProjectToDocumentMap> CreateProjectToDocumentMapAsync(Project
foreach (var document in documents)
{
finalMap.GetOrAdd(document.Project, s_createDocumentMap)
.Add(document, (symbol, finder));
.MultiAdd(document, (symbol, finder));
}
}

Expand Down Expand Up @@ -92,7 +90,7 @@ private async Task<ProjectMap> CreateProjectMapAsync(ConcurrentSet<ISymbol> symb
{
if (scope == null || scope.Contains(project))
{
projectMap.Add(project, (symbol, finder));
projectMap.MultiAdd(project, (symbol, finder));
}
}
}
Expand Down Expand Up @@ -151,8 +149,7 @@ private async Task DetermineAllSymbolsCoreAsync(

// Defer to the language to see if it wants to cascade here in some special way.
var symbolProject = _solution.GetProject(searchSymbol.ContainingAssembly);
var service = symbolProject?.LanguageServices.GetService<ILanguageServiceReferenceFinder>();
if (service != null)
if (symbolProject?.LanguageServices.GetService<ILanguageServiceReferenceFinder>() is { } service)
{
symbols = await service.DetermineCascadedSymbolsAsync(
searchSymbol, symbolProject, _cancellationToken).ConfigureAwait(false);
Expand Down Expand Up @@ -185,7 +182,7 @@ private void AddSymbolTasks(
}
}

private ImmutableHashSet<Project> GetProjectScope()
private ImmutableHashSet<Project>? GetProjectScope()
{
if (_documents == null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,15 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#nullable disable

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.FindSymbols.Finders;
using Microsoft.CodeAnalysis.Internal.Log;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.FindSymbols
{
using DocumentMap = MultiDictionary<Document, (ISymbol symbol, IReferenceFinder finder)>;
using DocumentMap = Dictionary<Document, HashSet<(ISymbol symbol, IReferenceFinder finder)>>;

internal partial class FindReferencesSearchEngine
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ protected sealed override bool CanFind(TSymbol symbol)
protected override Task<ImmutableArray<Document>> DetermineDocumentsToSearchAsync(
TSymbol symbol,
Project project,
IImmutableSet<Document> documents,
IImmutableSet<Document>? documents,
FindReferencesSearchOptions options,
CancellationToken cancellationToken)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ protected AbstractMethodOrPropertyOrEventSymbolReferenceFinder()
protected override async Task<ImmutableArray<ISymbol>> DetermineCascadedSymbolsAsync(
TSymbol symbol,
Solution solution,
IImmutableSet<Project> projects,
IImmutableSet<Project>? projects,
FindReferencesSearchOptions options,
CancellationToken cancellationToken)
{
Expand Down
Loading

0 comments on commit 9f079e2

Please sign in to comment.