-
Notifications
You must be signed in to change notification settings - Fork 5.4k
Speed up KeyAnalyser #87590
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
Speed up KeyAnalyser #87590
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,9 +4,7 @@ | |
| using System.Buffers; | ||
| using System.Collections.Generic; | ||
| using System.Diagnostics; | ||
| #if !NET8_0_OR_GREATER | ||
| using System.Runtime.CompilerServices; | ||
| #endif | ||
|
|
||
| namespace System.Collections.Frozen | ||
| { | ||
|
|
@@ -59,7 +57,7 @@ private static bool TryUseSubstring(ReadOnlySpan<string> uniqueStrings, bool ign | |
| int maxSubstringLength = Math.Min(minLength, MaxSubstringLengthLimit); | ||
| for (int count = 1; count <= maxSubstringLength; count++) | ||
| { | ||
| comparer.IsLeft = true; | ||
| comparer.Slicer = SliceLeft; | ||
| comparer.Count = count; | ||
|
|
||
| // For each index, get a uniqueness factor for the left-justified substrings. | ||
|
|
@@ -71,8 +69,7 @@ private static bool TryUseSubstring(ReadOnlySpan<string> uniqueStrings, bool ign | |
| if (HasSufficientUniquenessFactor(set, uniqueStrings)) | ||
| { | ||
| results = CreateAnalysisResults( | ||
| uniqueStrings, ignoreCase, minLength, maxLength, index, count, | ||
| static (string s, int index, int count) => s.AsSpan(index, count)); | ||
| uniqueStrings, ignoreCase, minLength, maxLength, index, count, comparer.Slicer); | ||
| return true; | ||
| } | ||
| } | ||
|
|
@@ -83,8 +80,8 @@ private static bool TryUseSubstring(ReadOnlySpan<string> uniqueStrings, bool ign | |
| // right-justified substrings, and so we also check right-justification. | ||
| if (minLength != maxLength) | ||
| { | ||
| // toggle the direction and re-use the comparer and hashset (HasSufficientUniquenessFactor clears it) | ||
| comparer.IsLeft = false; | ||
| // toggle the direction and re-use the comparer and HashSet (HasSufficientUniquenessFactor clears it) | ||
| comparer.Slicer = SliceRight; | ||
|
|
||
| // For each index, get a uniqueness factor for the right-justified substrings. | ||
| // If any is above our threshold, we're done. | ||
|
|
@@ -96,8 +93,7 @@ private static bool TryUseSubstring(ReadOnlySpan<string> uniqueStrings, bool ign | |
| if (HasSufficientUniquenessFactor(set, uniqueStrings)) | ||
| { | ||
| results = CreateAnalysisResults( | ||
| uniqueStrings, ignoreCase, minLength, maxLength, comparer.Index, count, | ||
| static (string s, int index, int count) => s.AsSpan(s.Length + index, count)); | ||
| uniqueStrings, ignoreCase, minLength, maxLength, comparer.Index, count, comparer.Slicer); | ||
| return true; | ||
| } | ||
| } | ||
|
|
@@ -110,7 +106,7 @@ private static bool TryUseSubstring(ReadOnlySpan<string> uniqueStrings, bool ign | |
| } | ||
|
|
||
| private static AnalysisResults CreateAnalysisResults( | ||
| ReadOnlySpan<string> uniqueStrings, bool ignoreCase, int minLength, int maxLength, int index, int count, GetSpan getSubstringSpan) | ||
| ReadOnlySpan<string> uniqueStrings, bool ignoreCase, int minLength, int maxLength, int index, int count, GetSpan slicer) | ||
| { | ||
| // Start off by assuming all strings are ASCII | ||
| bool allAsciiIfIgnoreCase = true; | ||
|
|
@@ -124,12 +120,12 @@ private static AnalysisResults CreateAnalysisResults( | |
| // actually perform the comparison as case-sensitive even if case-insensitive | ||
| // was requested, as there's nothing that would compare equally to the substring | ||
| // other than the substring itself. | ||
| bool canSwitchIgnoreCaseToCaseSensitive = ignoreCase; | ||
| bool canSwitchIgnoreCaseToCaseSensitive = true; | ||
|
|
||
| foreach (string s in uniqueStrings) | ||
| { | ||
| // Get the span for the substring. | ||
| ReadOnlySpan<char> substring = getSubstringSpan(s, index, count); | ||
| ReadOnlySpan<char> substring = slicer(s, index, count); | ||
|
|
||
| // If the substring isn't ASCII, bail out to return the results. | ||
| if (!IsAllAscii(substring)) | ||
|
|
@@ -263,25 +259,32 @@ public AnalysisResults(bool ignoreCase, bool allAsciiIfIgnoreCase, int hashIndex | |
| public bool RightJustifiedSubstring => HashIndex < 0; | ||
| } | ||
|
|
||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
| private static ReadOnlySpan<char> SliceLeft(string s, int index, int count) => s.AsSpan(index, count); | ||
|
|
||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
| private static ReadOnlySpan<char> SliceRight(string s, int index, int count) => s.AsSpan(s.Length + index, count); | ||
|
|
||
| private abstract class SubstringComparer : IEqualityComparer<string> | ||
| { | ||
| public int Index; | ||
| public int Count; | ||
| public bool IsLeft; | ||
| public GetSpan Slicer = SliceLeft; | ||
|
|
||
| public abstract bool Equals(string? x, string? y); | ||
| public abstract int GetHashCode(string s); | ||
| } | ||
|
|
||
| private sealed class JustifiedSubstringComparer : SubstringComparer | ||
| { | ||
| public override bool Equals(string? x, string? y) => x.AsSpan(IsLeft ? Index : (x!.Length + Index), Count).SequenceEqual(y.AsSpan(IsLeft ? Index : (y!.Length + Index), Count)); | ||
| public override int GetHashCode(string s) => Hashing.GetHashCodeOrdinal(s.AsSpan(IsLeft ? Index : (s.Length + Index), Count)); | ||
| public override bool Equals(string? x, string? y) => Slicer(x!, Index, Count).SequenceEqual(Slicer(y!, Index, Count)); | ||
|
||
| public override int GetHashCode(string s) => Hashing.GetHashCodeOrdinal(Slicer(s, Index, Count)); | ||
| } | ||
|
|
||
| private sealed class JustifiedCaseInsensitiveSubstringComparer : SubstringComparer | ||
| { | ||
| public override bool Equals(string? x, string? y) => x.AsSpan(IsLeft ? Index : (x!.Length + Index), Count).Equals(y.AsSpan(IsLeft ? Index : (y!.Length + Index), Count), StringComparison.OrdinalIgnoreCase); | ||
| public override int GetHashCode(string s) => Hashing.GetHashCodeOrdinalIgnoreCase(s.AsSpan(IsLeft ? Index : (s.Length + Index), Count)); | ||
| public override bool Equals(string? x, string? y) => Slicer(x!, Index, Count).Equals(Slicer(y!, Index, Count), StringComparison.OrdinalIgnoreCase); | ||
| public override int GetHashCode(string s) => Hashing.GetHashCodeOrdinalIgnoreCase(Slicer(s, Index, Count)); | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is demonstrably
truebecause we're inside theif (ignoreCase)immediately above and could be a distinct PR, but it actually changes nothing but the readability and not hoping the Compiler/JIT groks this truth.