Skip to content

Commit 92a6608

Browse files
committed
avoid the string alloc in GetHashCode (when possible)
1 parent 0c02523 commit 92a6608

1 file changed

Lines changed: 31 additions & 11 deletions

File tree

src/StackExchange.Redis/RedisValue.cs

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -409,18 +409,38 @@ public override bool Equals(object? obj)
409409
private static int GetHashCode(RedisValue x)
410410
{
411411
x = x.Simplify();
412-
return x.Type switch
412+
switch (x.Type)
413413
{
414-
StorageType.Null => -1,
415-
StorageType.Double => x.OverlappedValueDouble.GetHashCode(),
416-
StorageType.Int64 or StorageType.UInt64 => x._valueInt64.GetHashCode(),
417-
// Everything else - strings *and* byte/memory/sequence buffers - compares to each other "as
418-
// strings" (see operator ==): e.g. "inf" the string equals "inf" the bytes. A buffer that
419-
// looked numeric has already been reduced to Int64/Double by Simplify() above, so the only
420-
// equality-consistent hash for what remains is the hash of the string form. (Note we must NOT
421-
// hash raw bytes here: that would give byte buffers a different hash from the equal string.)
422-
_ => ((string)x!).GetHashCode(),
423-
};
414+
case StorageType.Null:
415+
return -1;
416+
case StorageType.Double:
417+
return x.OverlappedValueDouble.GetHashCode();
418+
case StorageType.Int64 or StorageType.UInt64:
419+
return x._valueInt64.GetHashCode();
420+
case StorageType.String:
421+
return x.RawString().GetHashCode();
422+
}
423+
424+
// Everything else - byte/memory/sequence buffers - compares to each other (and to strings) "as
425+
// strings" (see operator ==): e.g. "inf" the bytes equals "inf" the string. Anything that looked
426+
// numeric was already reduced to Int64/Double by Simplify() above, so the equality-consistent
427+
// hash for what remains is the hash of the string form. (We must NOT hash raw bytes: that would
428+
// give byte buffers a different hash from the equal string.)
429+
#if NET
430+
// hash the decoded UTF8 chars directly, which avoids allocating a transient string; this matches
431+
// string.GetHashCode() for the equivalent text
432+
const int StackLimit = 256;
433+
var maxChars = x.GetMaxCharCount();
434+
char[]? leased = null;
435+
Span<char> chars = maxChars <= StackLimit ? stackalloc char[StackLimit] : (leased = ArrayPool<char>.Shared.Rent(maxChars));
436+
var written = x.CopyTo(chars);
437+
var hashCode = string.GetHashCode(chars.Slice(0, written));
438+
if (leased is not null) ArrayPool<char>.Shared.Return(leased);
439+
return hashCode;
440+
#else
441+
// no string.GetHashCode(ReadOnlySpan<char>) on these targets, so fall back to the string form
442+
return ((string)x!).GetHashCode();
443+
#endif
424444
}
425445

426446
/// <summary>

0 commit comments

Comments
 (0)