Skip to content

Commit dd6328a

Browse files
author
Alex Peck
committed
alt outline
1 parent 31509b2 commit dd6328a

File tree

2 files changed

+148
-0
lines changed

2 files changed

+148
-0
lines changed

BitFaster.Caching/Lru/ConcurrentLruCore.cs

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -850,6 +850,151 @@ private static Optional<ICacheEvents<K, V>> CreateEvents(ConcurrentLruCore<K, V,
850850
return new(new Proxy(lru));
851851
}
852852

853+
#if NET9_0_OR_GREATER
854+
855+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
856+
private static bool IsCompatibleKey<TAlternateKey>(ConcurrentDictionary<K, I> d)
857+
where TAlternateKey : notnull, allows ref struct
858+
{
859+
return d.Comparer is IAlternateEqualityComparer<TAlternateKey, K>;
860+
}
861+
862+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
863+
private static IAlternateEqualityComparer<TAlternateKey, K> GetAlternateComparer<TAlternateKey>(ConcurrentDictionary<K, I> d)
864+
where TAlternateKey : notnull, allows ref struct
865+
{
866+
Debug.Assert(IsCompatibleKey<TAlternateKey>(d));
867+
return Unsafe.As<IAlternateEqualityComparer<TAlternateKey, K>>(d.Comparer!);
868+
}
869+
870+
public IAlternateCache<TAlternateKey, K, V> GetAlternateCache<TAlternateKey>() where TAlternateKey : notnull, allows ref struct
871+
{
872+
if (!IsCompatibleKey<TAlternateKey>(this.dictionary))
873+
{
874+
Throw.IncompatibleComparer();
875+
}
876+
877+
return new AlternateCache<TAlternateKey>(this);
878+
}
879+
880+
public bool TryGetAlternateCache<TAlternateKey>([MaybeNullWhen(false)] out IAlternateCache<TAlternateKey, K, V> lookup) where TAlternateKey : notnull, allows ref struct
881+
{
882+
if (IsCompatibleKey<TAlternateKey>(this.dictionary))
883+
{
884+
lookup = new AlternateCache<TAlternateKey>(this);
885+
return true;
886+
}
887+
888+
lookup = default;
889+
return false;
890+
}
891+
892+
// Rough idea of alternate cache interface
893+
// Note: we need a sync and async variant, plumbed into ICache and IAsyncCache.
894+
public interface IAlternateCache<TAlternateKey, K, V> where TAlternateKey : notnull, allows ref struct
895+
{
896+
bool TryGet(TAlternateKey key, [MaybeNullWhen(false)] out V value);
897+
898+
bool TryRemove(TAlternateKey key, [MaybeNullWhen(false)] out K actualKey, [MaybeNullWhen(false)] out V value);
899+
900+
V GetOrAdd(TAlternateKey altKey, Func<TAlternateKey, V> valueFactory);
901+
902+
V GetOrAdd<TArg>(TAlternateKey altKey, Func<TAlternateKey, TArg, V> valueFactory, TArg factoryArgument);
903+
904+
// TryUpdate
905+
// AddOrUpdate
906+
}
907+
908+
internal readonly struct AlternateCache<TAlternateKey> : IAlternateCache<TAlternateKey, K, V> where TAlternateKey : notnull, allows ref struct
909+
{
910+
/// <summary>Initialize the instance. The dictionary must have already been verified to have a compatible comparer.</summary>
911+
internal AlternateCache(ConcurrentLruCore<K, V, I, P, T> lru)
912+
{
913+
Debug.Assert(lru is not null);
914+
Debug.Assert(IsCompatibleKey<TAlternateKey>(lru.dictionary));
915+
Lru = lru;
916+
}
917+
918+
internal ConcurrentLruCore<K, V, I, P, T> Lru { get; }
919+
920+
public bool TryGet(TAlternateKey key, [MaybeNullWhen(false)] out V value)
921+
{
922+
var alternate = this.Lru.dictionary.GetAlternateLookup<TAlternateKey>();
923+
924+
if (alternate.TryGetValue(key, out var item))
925+
{
926+
return Lru.GetOrDiscard(item, out value);
927+
}
928+
929+
value = default;
930+
Lru.telemetryPolicy.IncrementMiss();
931+
return false;
932+
}
933+
934+
public bool TryRemove(TAlternateKey key, [MaybeNullWhen(false)] out K actualKey, [MaybeNullWhen(false)] out V value)
935+
{
936+
var alternate = this.Lru.dictionary.GetAlternateLookup<TAlternateKey>();
937+
938+
if (alternate.TryGetValue(key, out var item))
939+
{
940+
Lru.OnRemove(item.Key, item, ItemRemovedReason.Removed);
941+
actualKey = item.Key;
942+
value = item.Value;
943+
return true;
944+
}
945+
946+
actualKey = default;
947+
value = default;
948+
return false;
949+
}
950+
951+
public V GetOrAdd(TAlternateKey altKey, Func<TAlternateKey, V> valueFactory)
952+
{
953+
var alternate = this.Lru.dictionary.GetAlternateLookup<TAlternateKey>();
954+
955+
while (true)
956+
{
957+
if (alternate.TryGetValue(altKey, out var item))
958+
{
959+
return item.Value;
960+
}
961+
962+
// We cannot avoid allocating the key since it is required for item policy etc. Thus fall back to Lru for add.
963+
// The value factory may be called concurrently for the same key, but the first write to the dictionary wins.
964+
K key = GetAlternateComparer<TAlternateKey>(this.Lru.dictionary).Create(altKey);
965+
V value = valueFactory(altKey);
966+
if (Lru.TryAdd(key, value))
967+
{
968+
return value;
969+
}
970+
}
971+
}
972+
973+
public V GetOrAdd<TArg>(TAlternateKey altKey, Func<TAlternateKey, TArg, V> valueFactory, TArg factoryArgument)
974+
{
975+
var alternate = this.Lru.dictionary.GetAlternateLookup<TAlternateKey>();
976+
977+
while (true)
978+
{
979+
if (alternate.TryGetValue(altKey, out var item))
980+
{
981+
return item.Value;
982+
}
983+
984+
// We cannot avoid allocating the key since it is required for item policy etc. Thus fall back to Lru for add.
985+
// The value factory may be called concurrently for the same key, but the first write to the dictionary wins.
986+
K key = GetAlternateComparer<TAlternateKey>(this.Lru.dictionary).Create(altKey);
987+
V value = valueFactory(altKey, factoryArgument);
988+
if (Lru.TryAdd(key, value))
989+
{
990+
return value;
991+
}
992+
}
993+
}
994+
}
995+
996+
#endif
997+
853998
// To get JIT optimizations, policies must be structs.
854999
// If the structs are returned directly via properties, they will be copied. Since
8551000
// telemetryPolicy is a mutable struct, copy is bad. One workaround is to store the

BitFaster.Caching/Throw.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ internal static class Throw
2626
[DoesNotReturn]
2727
public static void Disposed<T>() => throw CreateObjectDisposedException<T>();
2828

29+
[DoesNotReturn]
30+
public static void IncompatibleComparer() => throw new InvalidOperationException("Incompatible comparer");
31+
2932
[MethodImpl(MethodImplOptions.NoInlining)]
3033
private static ArgumentNullException CreateArgumentNullException(ExceptionArgument arg) => new ArgumentNullException(GetArgumentString(arg));
3134

0 commit comments

Comments
 (0)