Skip to content
This repository has been archived by the owner on Jan 6, 2024. It is now read-only.

Commit

Permalink
[core] use StringComparer for Dictionary/HashSet (#14900)
Browse files Browse the repository at this point in the history
Context: dotnet/maui#12130
Context: https://github.com/angelru/CvSlowJittering

When profiling the above customer app while scrolling, 4% of the time
is spent doing dictionary lookups:

    (4.0%) System.Private.CoreLib!System.Collections.Generic.Dictionary<TKey_REF,TValue_REF>.FindValue(TKey_REF)

Observing the call stack, some of these are coming from culture-aware
string lookups in MAUI:

* `microsoft.maui!Microsoft.Maui.PropertyMapper.GetProperty(string)`
* `microsoft.maui!Microsoft.Maui.WeakEventManager.AddEventHandler(System.EventHandler`1<TEventArgs_REF>,string)`
* `microsoft.maui!Microsoft.Maui.CommandMapper.GetCommand(string)`

Which show up as a mixture of `string` comparers:

    (0.98%) System.Private.CoreLib!System.Collections.Generic.NonRandomizedStringEqualityComparer.OrdinalComparer.GetHashCode(string)
    (0.71%) System.Private.CoreLib!System.String.GetNonRandomizedHashCode()
    (0.31%) System.Private.CoreLib!System.Collections.Generic.NonRandomizedStringEqualityComparer.OrdinalComparer.Equals(string,stri
    (0.01%) System.Private.CoreLib!System.Collections.Generic.NonRandomizedStringEqualityComparer.GetStringComparer(object)

In cases of `Dictionary<string, TValue>` or `HashSet<string>`, we can
use `StringComparer.Ordinal` for faster dictionary lookups.

Unfortunately, there is no code analyzer for this:

dotnet/runtime#52399

So, I manually went through the codebase and found all the places.

I now only see the *fast* string comparers in this sample:

    (1.3%) System.Private.CoreLib!System.Collections.Generic.NonRandomizedStringEqualityComparer.OrdinalComparer.GetHashCode(string)
    (0.35%) System.Private.CoreLib!System.Collections.Generic.NonRandomizedStringEqualityComparer.OrdinalComparer.Equals(string,stri

Which is about ~0.36% better than before.

This should slightly improve the performance of handlers & all
controls on all platforms.

I also fixed `Microsoft.Maui.Graphics.Text.TextColors` to use
`StringComparer.OrdinalIgnoreCase` -- and removed a
`ToUpperInvariant()` call.
  • Loading branch information
jonathanpeppers authored May 3, 2023
1 parent c05a915 commit 9d47c00
Show file tree
Hide file tree
Showing 4 changed files with 5 additions and 5 deletions.
2 changes: 1 addition & 1 deletion src/TextToSpeech/TextToSpeech.android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ public async Task SpeakAsync(string text, int max, SpeechOptions options, Cancel
for (var i = 0; i < parts.Count && !cancelToken.IsCancellationRequested; i++)
{
// We require the utterance id to be set if we want the completed listener to fire
var map = new Dictionary<string, string>
var map = new Dictionary<string, string>(StringComparer.Ordinal)
{
{ AndroidTextToSpeech.Engine.KeyParamUtteranceId, $"{guid}.{i}" }
};
Expand Down
2 changes: 1 addition & 1 deletion src/Types/Shared/WebUtils.shared.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ static class WebUtils
{
internal static IDictionary<string, string> ParseQueryString(string url)
{
var d = new Dictionary<string, string>();
var d = new Dictionary<string, string>(StringComparer.Ordinal);

if (string.IsNullOrWhiteSpace(url) || (url.IndexOf("?", StringComparison.Ordinal) == -1 && url.IndexOf("#", StringComparison.Ordinal) == -1))
return d;
Expand Down
4 changes: 2 additions & 2 deletions src/VersionTracking/VersionTracking.shared.cs
Original file line number Diff line number Diff line change
Expand Up @@ -238,15 +238,15 @@ internal void InitVersionTracking()
IsFirstLaunchEver = !preferences.ContainsKey(versionsKey, sharedName) || !preferences.ContainsKey(buildsKey, sharedName);
if (IsFirstLaunchEver)
{
versionTrail = new Dictionary<string, List<string>>
versionTrail = new(StringComparer.Ordinal)
{
{ versionsKey, new List<string>() },
{ buildsKey, new List<string>() }
};
}
else
{
versionTrail = new Dictionary<string, List<string>>
versionTrail = new(StringComparer.Ordinal)
{
{ versionsKey, ReadHistory(versionsKey).ToList() },
{ buildsKey, ReadHistory(buildsKey).ToList() }
Expand Down
2 changes: 1 addition & 1 deletion src/WebAuthenticator/WebAuthenticatorResult.shared.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public WebAuthenticatorResult(IDictionary<string, string> properties)
/// <summary>
/// The dictionary of key/value pairs parsed form the callback URI's query string.
/// </summary>
public Dictionary<string, string> Properties { get; set; } = new Dictionary<string, string>();
public Dictionary<string, string> Properties { get; set; } = new(StringComparer.Ordinal);

/// <summary>Puts a key/value pair into the dictionary.</summary>
public void Put(string key, string value)
Expand Down

0 comments on commit 9d47c00

Please sign in to comment.