Skip to content

Commit

Permalink
Merge pull request #75297 from CyrusNajmabadi/threadLocal
Browse files Browse the repository at this point in the history
  • Loading branch information
CyrusNajmabadi authored Sep 30, 2024
2 parents efcb72e + d972d95 commit 8b0b65c
Showing 1 changed file with 12 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,12 @@ private sealed class CachingFilePathComparer : IEqualityComparer<string>
public static readonly CachingFilePathComparer Instance = new();

/// <summary>
/// Lock to protect the the last string and hash code we computed. `enableThreadOwnerTracking: false` as we
/// don't need that tracking, and it substantially speeds up the spin lock (removing 0.7% cpu from solution load
/// scenario).
/// ThreadStatic so that gets its own copy it can safely read/write from, removing the need for expensive
/// contentious locks. The purpose of this type is to allow lookup of the same key across N dictionaries
/// efficiently from the same thread. So this accomplishes that purpose.
/// </summary>
private SpinLock _lock = new(enableThreadOwnerTracking: false);
private string? _lastString;
private int _lastHashCode;
[ThreadStatic]
private static (string? lastString, int lastHashCode) s_data;

private CachingFilePathComparer()
{
Expand All @@ -51,61 +50,19 @@ public bool Equals(string? x, string? y)

public int GetHashCode([DisallowNull] string obj)
{
if (TryGetCachedHashCode(obj, out var hashCode))
return hashCode;
// SToub thinks this may be faster on NetFx as it will help the runtime with reading/writing from a single location.
ref var data = ref s_data;
if (ReferenceEquals(data.lastString, obj))
return data.lastHashCode;

// Hashing a different string than last time. Compute the hash and cache the value.

// Specialized impl of OrdinalIgnoreCase.GetHashCode that is faster for the common case of an all-ASCII
// string. Falls back to normal OrdinalIgnoreCase.GetHashCode for the uncommon case.
hashCode = GetNonRandomizedHashCodeOrdinalIgnoreCase(obj);
var hash = GetNonRandomizedHashCodeOrdinalIgnoreCase(obj);

var lockTaken = false;
try
{
_lock.Enter(ref lockTaken);
_lastString = obj;
_lastHashCode = hashCode;
}
finally
{
if (lockTaken)
_lock.Exit();
}

return hashCode;
}

private bool TryGetCachedHashCode(string obj, out int hashCode)
{
var lastString = _lastString;

// Quickly check if this is definitely *not* trying to hash the same string that this comparer was just used
// to hash. If that's the case, we can avoid taking the lock and just return false immediately. For the
// case when a lot of distinct strings are being hashed (say, when a dictionary is being populated), this
// means we only spin-wait once.
if (!ReferenceEquals(lastString, obj))
{
hashCode = default;
return false;
}

// Otherwise, take the lock, and now copy both the string and hash out.
var lockTaken = false;
try
{
_lock.Enter(ref lockTaken);
lastString = _lastString;
hashCode = _lastHashCode;
}
finally
{
if (lockTaken)
_lock.Exit();
}

// Check again, as another thread may have written into this field between the first check and taking the lock.
return ReferenceEquals(lastString, obj);
data = (obj, hash);
return hash;
}

// From https://github.com/dotnet/runtime/blob/5aa9687e110faa19d1165ba680e52585a822464d/src/libraries/System.Private.CoreLib/src/System/String.Comparison.cs#L921
Expand Down

0 comments on commit 8b0b65c

Please sign in to comment.