Skip to content

Commit

Permalink
Cache stateless comparers (#340)
Browse files Browse the repository at this point in the history
* Use ldtoken + castclass instead of ldtoken + isinst in TsavoriteEqualityComparer.Get

* JIT is more "used" to former pattern.

* Cache key comparers in Tsavorite

* Cache stateless comparers in Garnet

* i.e.ByteArrayComparer and SortedSetComparer
  • Loading branch information
PaulusParssinen authored May 1, 2024
1 parent 1827878 commit 918775d
Show file tree
Hide file tree
Showing 11 changed files with 99 additions and 107 deletions.
4 changes: 2 additions & 2 deletions libs/server/Objects/Hash/HashObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public unsafe partial class HashObject : GarnetObjectBase
public HashObject(long expiration = 0)
: base(expiration, MemoryUtils.DictionaryOverhead)
{
hash = new Dictionary<byte[], byte[]>(new ByteArrayComparer());
hash = new Dictionary<byte[], byte[]>(ByteArrayComparer.Instance);
}

/// <summary>
Expand All @@ -58,7 +58,7 @@ public HashObject(long expiration = 0)
public HashObject(BinaryReader reader)
: base(reader, MemoryUtils.DictionaryOverhead)
{
hash = new Dictionary<byte[], byte[]>(new ByteArrayComparer());
hash = new Dictionary<byte[], byte[]>(ByteArrayComparer.Instance);

int count = reader.ReadInt32();
for (int i = 0; i < count; i++)
Expand Down
4 changes: 2 additions & 2 deletions libs/server/Objects/Set/SetObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public unsafe partial class SetObject : GarnetObjectBase
public SetObject(long expiration = 0)
: base(expiration, MemoryUtils.HashSetOverhead)
{
set = new HashSet<byte[]>(new ByteArrayComparer());
set = new HashSet<byte[]>(ByteArrayComparer.Instance);
}

/// <summary>
Expand All @@ -55,7 +55,7 @@ public SetObject(long expiration = 0)
public SetObject(BinaryReader reader)
: base(reader, MemoryUtils.HashSetOverhead)
{
set = new HashSet<byte[]>(new ByteArrayComparer());
set = new HashSet<byte[]>(ByteArrayComparer.Instance);

int count = reader.ReadInt32();
for (int i = 0; i < count; i++)
Expand Down
15 changes: 6 additions & 9 deletions libs/server/Objects/SortedSet/SortedSetObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,20 +72,17 @@ public enum SortedSetOrderOperation
/// </summary>
public partial class SortedSetObject : GarnetObjectBase
{
readonly SortedSet<(double, byte[])> sortedSet;
readonly Dictionary<byte[], double> sortedSetDict;

static readonly SortedSetComparer sortedSetComparer = new();
static readonly ByteArrayComparer byteArrayComparer = new();
private readonly SortedSet<(double, byte[])> sortedSet;
private readonly Dictionary<byte[], double> sortedSetDict;

/// <summary>
/// Constructor
/// </summary>
public SortedSetObject(long expiration = 0)
: base(expiration, MemoryUtils.SortedSetOverhead + MemoryUtils.DictionaryOverhead)
{
sortedSet = new(sortedSetComparer);
sortedSetDict = new Dictionary<byte[], double>(byteArrayComparer);
sortedSet = new(SortedSetComparer.Instance);
sortedSetDict = new Dictionary<byte[], double>(ByteArrayComparer.Instance);
}

/// <summary>
Expand All @@ -94,8 +91,8 @@ public SortedSetObject(long expiration = 0)
public SortedSetObject(BinaryReader reader)
: base(reader, MemoryUtils.SortedSetOverhead + MemoryUtils.DictionaryOverhead)
{
sortedSet = new(sortedSetComparer);
sortedSetDict = new Dictionary<byte[], double>(byteArrayComparer);
sortedSet = new(SortedSetComparer.Instance);
sortedSetDict = new Dictionary<byte[], double>(ByteArrayComparer.Instance);

int count = reader.ReadInt32();
for (int i = 0; i < count; i++)
Expand Down
9 changes: 8 additions & 1 deletion libs/server/Objects/SortedSetComparer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,15 @@

namespace Garnet.server
{
class SortedSetComparer : IComparer<(double, byte[])>
public sealed class SortedSetComparer : IComparer<(double, byte[])>
{
/// <summary>
/// The default instance.
/// </summary>
/// <remarks>Used to avoid allocating new comparers.</remarks>
public static readonly SortedSetComparer Instance = new();

/// <inheritdoc/>
public int Compare((double, byte[]) x, (double, byte[]) y)
{
var ret = x.Item1.CompareTo(y.Item1);
Expand Down
10 changes: 5 additions & 5 deletions libs/server/PubSub/SubscribeBroker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ private async Task Start(CancellationToken cancellationToken = default)
{
try
{
var uniqueKeys = new Dictionary<byte[], (byte[], byte[])>(new ByteArrayComparer());
var uniqueKeys = new Dictionary<byte[], (byte[], byte[])>(ByteArrayComparer.Instance);
long truncateUntilAddress = log.BeginAddress;

while (true)
Expand Down Expand Up @@ -215,8 +215,8 @@ public unsafe int Subscribe(ref byte* key, ServerSessionBase session)
if (Interlocked.CompareExchange(ref publishQueue, new AsyncQueue<(byte[], byte[])>(), null) == null)
{
done.Reset();
subscriptions = new ConcurrentDictionary<byte[], ConcurrentDictionary<int, ServerSessionBase>>(new ByteArrayComparer());
prefixSubscriptions = new ConcurrentDictionary<byte[], (bool, ConcurrentDictionary<int, ServerSessionBase>)>(new ByteArrayComparer());
subscriptions = new ConcurrentDictionary<byte[], ConcurrentDictionary<int, ServerSessionBase>>(ByteArrayComparer.Instance);
prefixSubscriptions = new ConcurrentDictionary<byte[], (bool, ConcurrentDictionary<int, ServerSessionBase>)>(ByteArrayComparer.Instance);
Task.Run(() => Start(cts.Token));
}
else
Expand Down Expand Up @@ -245,8 +245,8 @@ public unsafe int PSubscribe(ref byte* prefix, ServerSessionBase session, bool a
if (Interlocked.CompareExchange(ref publishQueue, new AsyncQueue<(byte[], byte[])>(), null) == null)
{
done.Reset();
subscriptions = new ConcurrentDictionary<byte[], ConcurrentDictionary<int, ServerSessionBase>>(new ByteArrayComparer());
prefixSubscriptions = new ConcurrentDictionary<byte[], (bool, ConcurrentDictionary<int, ServerSessionBase>)>(new ByteArrayComparer());
subscriptions = new ConcurrentDictionary<byte[], ConcurrentDictionary<int, ServerSessionBase>>(ByteArrayComparer.Instance);
prefixSubscriptions = new ConcurrentDictionary<byte[], (bool, ConcurrentDictionary<int, ServerSessionBase>)>(ByteArrayComparer.Instance);
Task.Run(() => Start(cts.Token));
}
else
Expand Down
17 changes: 7 additions & 10 deletions libs/server/Resp/ByteArrayComparer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,19 @@ namespace Garnet.server
/// <summary>
/// Byte array equality comparer
/// </summary>
public class ByteArrayComparer : IEqualityComparer<byte[]>
public sealed class ByteArrayComparer : IEqualityComparer<byte[]>
{
/// <summary>
/// Equals
/// The default instance.
/// </summary>
/// <param name="left"></param>
/// <param name="right"></param>
/// <returns></returns>
/// <remarks>Used to avoid allocating new comparers.</remarks>
public static readonly ByteArrayComparer Instance = new();

/// <inheritdoc />
public bool Equals(byte[] left, byte[] right)
=> new ReadOnlySpan<byte>(left).SequenceEqual(new ReadOnlySpan<byte>(right));

/// <summary>
/// Get hash code
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
/// <inheritdoc />
public unsafe int GetHashCode(byte[] key)
{
fixed (byte* k = key)
Expand Down
6 changes: 3 additions & 3 deletions libs/server/Storage/Session/ObjectStore/SetOps.cs
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ internal unsafe GarnetStatus SetMove(ArgSlice sourceKey, ArgSlice destinationKey
/// <returns></returns>
public GarnetStatus SetUnion(ArgSlice[] keys, out HashSet<byte[]> output)
{
output = new HashSet<byte[]>(new ByteArrayComparer());
output = new HashSet<byte[]>(ByteArrayComparer.Instance);

if (keys.Length == 0)
return GarnetStatus.OK;
Expand Down Expand Up @@ -511,7 +511,7 @@ public GarnetStatus SetUnionStore(byte[] key, ArgSlice[] keys, out int count)
private HashSet<byte[]> SetUnion<TObjectContext>(ArgSlice[] keys, ref TObjectContext objectContext)
where TObjectContext : ITsavoriteContext<byte[], IGarnetObject, SpanByte, GarnetObjectStoreOutput, long>
{
var result = new HashSet<byte[]>(new ByteArrayComparer());
var result = new HashSet<byte[]>(ByteArrayComparer.Instance);
if (keys.Length == 0)
{
return result;
Expand Down Expand Up @@ -737,7 +737,7 @@ private HashSet<byte[]> SetDiff<TObjectContext>(ArgSlice[] keys, ref TObjectCont
{
if (first.garnetObject is SetObject firstObject)
{
result = new HashSet<byte[]>(firstObject.Set, new ByteArrayComparer());
result = new HashSet<byte[]>(firstObject.Set, ByteArrayComparer.Instance);
}
}
else
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

using System.Collections.Generic;

namespace Tsavorite.core
{
/// <summary>
/// Key interface
/// Defines methods to support the comparison of Tsavorite keys for equality.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="T">The type of keys to compare.</typeparam>
/// <remarks>This comparer differs from the built-in <see cref="IEqualityComparer{T}"/> in that it implements a 64-bit hash code</remarks>
public interface ITsavoriteEqualityComparer<T>
{
/// <summary>
/// Get 64-bit hash code
/// </summary>
/// <returns></returns>
long GetHashCode64(ref T k);
long GetHashCode64(ref T key);

/// <summary>
/// Equality comparison
/// </summary>
/// <param name="k1">Left side</param>
/// <param name="k2">Right side</param>
/// <returns></returns>
bool Equals(ref T k1, ref T k2);
}
}
Loading

0 comments on commit 918775d

Please sign in to comment.