diff --git a/BitFaster.Caching/Lfu/CmSketchCore.cs b/BitFaster.Caching/Lfu/CmSketchCore.cs
index de255840..0ea4223d 100644
--- a/BitFaster.Caching/Lfu/CmSketchCore.cs
+++ b/BitFaster.Caching/Lfu/CmSketchCore.cs
@@ -1,333 +1,338 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
-
-
-#if !NETSTANDARD2_0
-using System.Runtime.Intrinsics;
-using System.Runtime.Intrinsics.X86;
-#endif
-
-namespace BitFaster.Caching.Lfu
-{
- ///
- /// A probabilistic data structure used to estimate the frequency of a given value. Periodic aging reduces the
- /// accumulated count across all values over time, such that a historic popular value will decay to zero frequency
- /// over time if it is not accessed.
- ///
- ///
- /// The maximum frequency of an element is limited to 15 (4-bits). Each element is hashed to a 64 byte 'block'
- /// consisting of 4 segments of 32 4-bit counters. The 64 byte blocks are the same size as x64 L1 cache lines.
- /// While the blocks are not guaranteed to be aligned, this scheme minimizes L1 cache misses resulting in a
- /// significant speedup. When supported, a vectorized AVX2 code path provides a further speedup. Together, block
- /// and AVX2 are approximately 2x faster than the original implementation.
- ///
- /// This is a direct C# translation of FrequencySketch in the Caffeine library by ben.manes@gmail.com (Ben Manes).
- /// https://github.com/ben-manes/caffeine
- public class CmSketchCore
- where T : notnull
- where I : struct, IsaProbe
- {
- private const long ResetMask = 0x7777777777777777L;
- private const long OneMask = 0x1111111111111111L;
-
- private long[] table;
- private int sampleSize;
- private int blockMask;
- private int size;
-
- private readonly IEqualityComparer comparer;
-
- ///
- /// Initializes a new instance of the CmSketch class with the specified maximum size and equality comparer.
- ///
- /// The maximum size.
- /// The equality comparer.
- public CmSketchCore(long maximumSize, IEqualityComparer comparer)
- {
- EnsureCapacity(maximumSize);
- this.comparer = comparer;
- }
-
- ///
- /// Gets the reset sample size.
- ///
- public int ResetSampleSize => this.sampleSize;
-
- ///
- /// Gets the size.
- ///
- public int Size => this.size;
-
- ///
- /// Estimate the frequency of the specified value, up to the maximum of 15.
- ///
- /// The value.
- /// The estimated frequency of the value.
- public int EstimateFrequency(T value)
- {
-#if NETSTANDARD2_0
- return EstimateFrequencyStd(value);
-#else
-
- I isa = default;
-
- if (isa.IsAvx2Supported)
- {
- return EstimateFrequencyAvx(value);
- }
- else
- {
- return EstimateFrequencyStd(value);
- }
-#endif
- }
-
- ///
- /// Increment the count of the specified value.
- ///
- /// The value.
- public void Increment(T value)
- {
-#if NETSTANDARD2_0
- IncrementStd(value);
-#else
-
- I isa = default;
-
- if (isa.IsAvx2Supported)
- {
- IncrementAvx(value);
- }
- else
- {
- IncrementStd(value);
- }
-#endif
- }
-
- ///
- /// Clears the count for all items.
- ///
- public void Clear()
- {
- table = new long[table.Length];
- size = 0;
- }
-
- [MemberNotNull(nameof(table))]
- private void EnsureCapacity(long maximumSize)
- {
- int maximum = (int)Math.Min(maximumSize, int.MaxValue >> 1);
-
- table = new long[Math.Max(BitOps.CeilingPowerOfTwo(maximum), 8)];
- blockMask = (int)((uint)table.Length >> 3) - 1;
- sampleSize = (maximumSize == 0) ? 10 : (10 * maximum);
-
- size = 0;
- }
-
- private unsafe int EstimateFrequencyStd(T value)
- {
- var count = stackalloc int[4];
- int blockHash = Spread(comparer.GetHashCode(value));
- int counterHash = Rehash(blockHash);
- int block = (blockHash & blockMask) << 3;
-
- for (int i = 0; i < 4; i++)
- {
- int h = (int)((uint)counterHash >> (i << 3));
- int index = (h >> 1) & 15;
- int offset = h & 1;
- count[i] = (int)(((ulong)table[block + offset + (i << 1)] >> (index << 2)) & 0xfL);
- }
- return Math.Min(Math.Min(count[0], count[1]), Math.Min(count[2], count[3]));
- }
-
- private unsafe void IncrementStd(T value)
- {
- var index = stackalloc int[8];
- int blockHash = Spread(comparer.GetHashCode(value));
- int counterHash = Rehash(blockHash);
- int block = (blockHash & blockMask) << 3;
-
- for (int i = 0; i < 4; i++)
- {
- int h = (int)((uint)counterHash >> (i << 3));
- index[i] = (h >> 1) & 15;
- int offset = h & 1;
- index[i + 4] = block + offset + (i << 1);
- }
-
- bool added =
- IncrementAt(index[4], index[0])
- | IncrementAt(index[5], index[1])
- | IncrementAt(index[6], index[2])
- | IncrementAt(index[7], index[3]);
-
- if (added && (++size == sampleSize))
- {
- Reset();
- }
- }
-
- // Applies another round of hashing for additional randomization
- private static int Rehash(int x)
- {
- x = (int)(x * 0x31848bab);
- x ^= (int)((uint)x >> 14);
- return x;
- }
-
- // Applies a supplemental hash functions to defends against poor quality hash.
- private static int Spread(int x)
- {
- x ^= (int)((uint)x >> 17);
- x = (int)(x * 0xed5ad4bb);
- x ^= (int)((uint)x >> 11);
- x = (int)(x * 0xac4c1b51);
- x ^= (int)((uint)x >> 15);
- return x;
- }
-
- private bool IncrementAt(int i, int j)
- {
- int offset = j << 2;
- long mask = (0xfL << offset);
-
- if ((table[i] & mask) != mask)
- {
- table[i] += (1L << offset);
- return true;
- }
-
- return false;
- }
-
- private void Reset()
- {
- // unroll, almost 2x faster
- int count0 = 0;
- int count1 = 0;
- int count2 = 0;
- int count3 = 0;
-
- for (int i = 0; i < table.Length; i += 4)
- {
- count0 += BitOps.BitCount(table[i] & OneMask);
- count1 += BitOps.BitCount(table[i + 1] & OneMask);
- count2 += BitOps.BitCount(table[i + 2] & OneMask);
- count3 += BitOps.BitCount(table[i + 3] & OneMask);
-
- table[i] = (long)((ulong)table[i] >> 1) & ResetMask;
- table[i + 1] = (long)((ulong)table[i + 1] >> 1) & ResetMask;
- table[i + 2] = (long)((ulong)table[i + 2] >> 1) & ResetMask;
- table[i + 3] = (long)((ulong)table[i + 3] >> 1) & ResetMask;
- }
-
- count0 = (count0 + count1) + (count2 + count3);
-
- size = (size - (count0 >> 2)) >> 1;
- }
-
-#if !NETSTANDARD2_0
- private unsafe int EstimateFrequencyAvx(T value)
- {
- int blockHash = Spread(comparer.GetHashCode(value));
- int counterHash = Rehash(blockHash);
- int block = (blockHash & blockMask) << 3;
-
- Vector128 h = Vector128.Create(counterHash);
- h = Avx2.ShiftRightLogicalVariable(h.AsUInt32(), Vector128.Create(0U, 8U, 16U, 24U)).AsInt32();
-
- var index = Avx2.ShiftRightLogical(h, 1);
- index = Avx2.And(index, Vector128.Create(15)); // j - counter index
- Vector128 offset = Avx2.And(h, Vector128.Create(1));
- Vector128 blockOffset = Avx2.Add(Vector128.Create(block), offset); // i - table index
- blockOffset = Avx2.Add(blockOffset, Vector128.Create(0, 2, 4, 6)); // + (i << 1)
-
- fixed (long* tablePtr = table)
- {
- Vector256 tableVector = Avx2.GatherVector256(tablePtr, blockOffset, 8);
- index = Avx2.ShiftLeftLogical(index, 2);
-
- // convert index from int to long via permute
- Vector256 indexLong = Vector256.Create(index, Vector128.Zero).AsInt64();
- Vector256 permuteMask2 = Vector256.Create(0, 4, 1, 5, 2, 5, 3, 7);
- indexLong = Avx2.PermuteVar8x32(indexLong.AsInt32(), permuteMask2).AsInt64();
- tableVector = Avx2.ShiftRightLogicalVariable(tableVector, indexLong.AsUInt64());
- tableVector = Avx2.And(tableVector, Vector256.Create(0xfL));
-
- Vector256 permuteMask = Vector256.Create(0, 2, 4, 6, 1, 3, 5, 7);
- Vector128 count = Avx2.PermuteVar8x32(tableVector.AsInt32(), permuteMask)
- .GetLower()
- .AsUInt16();
-
- // set the zeroed high parts of the long value to ushort.Max
-#if NET6_0
- count = Avx2.Blend(count, Vector128.AllBitsSet, 0b10101010);
-#else
- count = Avx2.Blend(count, Vector128.Create(ushort.MaxValue), 0b10101010);
-#endif
-
- return Avx2.MinHorizontal(count).GetElement(0);
- }
- }
-
- private unsafe void IncrementAvx(T value)
- {
- int blockHash = Spread(comparer.GetHashCode(value));
- int counterHash = Rehash(blockHash);
- int block = (blockHash & blockMask) << 3;
-
- Vector128 h = Vector128.Create(counterHash);
- h = Avx2.ShiftRightLogicalVariable(h.AsUInt32(), Vector128.Create(0U, 8U, 16U, 24U)).AsInt32();
-
- Vector128 index = Avx2.ShiftRightLogical(h, 1);
- index = Avx2.And(index, Vector128.Create(15)); // j - counter index
- Vector128 offset = Avx2.And(h, Vector128.Create(1));
- Vector128 blockOffset = Avx2.Add(Vector128.Create(block), offset); // i - table index
- blockOffset = Avx2.Add(blockOffset, Vector128.Create(0, 2, 4, 6)); // + (i << 1)
-
- fixed (long* tablePtr = table)
- {
- Vector256 tableVector = Avx2.GatherVector256(tablePtr, blockOffset, 8);
-
- // j == index
- index = Avx2.ShiftLeftLogical(index, 2);
- Vector256 offsetLong = Vector256.Create(index, Vector128.Zero).AsInt64();
-
- Vector256 permuteMask = Vector256.Create(0, 4, 1, 5, 2, 5, 3, 7);
- offsetLong = Avx2.PermuteVar8x32(offsetLong.AsInt32(), permuteMask).AsInt64();
-
- // mask = (0xfL << offset)
- Vector256 fifteen = Vector256.Create(0xfL);
- Vector256 mask = Avx2.ShiftLeftLogicalVariable(fifteen, offsetLong.AsUInt64());
-
- // (table[i] & mask) != mask)
- // Note masked is 'equal' - therefore use AndNot below
- Vector256 masked = Avx2.CompareEqual(Avx2.And(tableVector, mask), mask);
-
- // 1L << offset
- Vector256 inc = Avx2.ShiftLeftLogicalVariable(Vector256.Create(1L), offsetLong.AsUInt64());
-
- // Mask to zero out non matches (add zero below) - first operand is NOT then AND result (order matters)
- inc = Avx2.AndNot(masked, inc);
-
- Vector256 result = Avx2.CompareEqual(masked.AsByte(), Vector256.Zero);
- bool wasInc = Avx2.MoveMask(result.AsByte()) == unchecked((int)(0b1111_1111_1111_1111_1111_1111_1111_1111));
-
- tablePtr[blockOffset.GetElement(0)] += inc.GetElement(0);
- tablePtr[blockOffset.GetElement(1)] += inc.GetElement(1);
- tablePtr[blockOffset.GetElement(2)] += inc.GetElement(2);
- tablePtr[blockOffset.GetElement(3)] += inc.GetElement(3);
-
- if (wasInc && (++size == sampleSize))
- {
- Reset();
- }
- }
- }
-#endif
- }
-}
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+
+
+#if !NETSTANDARD2_0
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.X86;
+#endif
+
+namespace BitFaster.Caching.Lfu
+{
+ ///
+ /// A probabilistic data structure used to estimate the frequency of a given value. Periodic aging reduces the
+ /// accumulated count across all values over time, such that a historic popular value will decay to zero frequency
+ /// over time if it is not accessed.
+ ///
+ ///
+ /// The maximum frequency of an element is limited to 15 (4-bits). Each element is hashed to a 64 byte 'block'
+ /// consisting of 4 segments of 32 4-bit counters. The 64 byte blocks are the same size as x64 L1 cache lines.
+ /// While the blocks are not guaranteed to be aligned, this scheme minimizes L1 cache misses resulting in a
+ /// significant speedup. When supported, a vectorized AVX2 code path provides a further speedup. Together, block
+ /// and AVX2 are approximately 2x faster than the original implementation.
+ ///
+ /// This is a direct C# translation of FrequencySketch in the Caffeine library by ben.manes@gmail.com (Ben Manes).
+ /// https://github.com/ben-manes/caffeine
+ public class CmSketchCore
+ where T : notnull
+ where I : struct, IsaProbe
+ {
+ private const long ResetMask = 0x7777777777777777L;
+ private const long OneMask = 0x1111111111111111L;
+
+ private long[] table;
+ private int sampleSize;
+ private int blockMask;
+ private int size;
+
+ private readonly IEqualityComparer comparer;
+
+ ///
+ /// Initializes a new instance of the CmSketch class with the specified maximum size and equality comparer.
+ ///
+ /// The maximum size.
+ /// The equality comparer.
+ public CmSketchCore(long maximumSize, IEqualityComparer comparer)
+ {
+ EnsureCapacity(maximumSize);
+ this.comparer = comparer;
+ }
+
+ ///
+ /// Gets the reset sample size.
+ ///
+ public int ResetSampleSize => this.sampleSize;
+
+ ///
+ /// Gets the size.
+ ///
+ public int Size => this.size;
+
+ ///
+ /// Estimate the frequency of the specified value, up to the maximum of 15.
+ ///
+ /// The value.
+ /// The estimated frequency of the value.
+ public int EstimateFrequency(T value)
+ {
+#if NETSTANDARD2_0
+ return EstimateFrequencyStd(value);
+#else
+
+ I isa = default;
+
+ if (isa.IsAvx2Supported)
+ {
+ return EstimateFrequencyAvx(value);
+ }
+ else
+ {
+ return EstimateFrequencyStd(value);
+ }
+#endif
+ }
+
+ ///
+ /// Increment the count of the specified value.
+ ///
+ /// The value.
+ public void Increment(T value)
+ {
+#if NETSTANDARD2_0
+ IncrementStd(value);
+#else
+
+ I isa = default;
+
+ if (isa.IsAvx2Supported)
+ {
+ IncrementAvx(value);
+ }
+ else
+ {
+ IncrementStd(value);
+ }
+#endif
+ }
+
+ ///
+ /// Clears the count for all items.
+ ///
+ public void Clear()
+ {
+ table = new long[table.Length];
+ size = 0;
+ }
+
+ [MemberNotNull(nameof(table))]
+ private void EnsureCapacity(long maximumSize)
+ {
+ int maximum = (int)Math.Min(maximumSize, int.MaxValue >> 1);
+
+ table = new long[Math.Max(BitOps.CeilingPowerOfTwo(maximum), 8)];
+ blockMask = (int)((uint)table.Length >> 3) - 1;
+ sampleSize = (maximumSize == 0) ? 10 : (10 * maximum);
+
+ size = 0;
+ }
+
+ [MethodImpl((MethodImplOptions)512)]
+ private unsafe int EstimateFrequencyStd(T value)
+ {
+ var count = stackalloc int[4];
+ int blockHash = Spread(comparer.GetHashCode(value));
+ int counterHash = Rehash(blockHash);
+ int block = (blockHash & blockMask) << 3;
+
+ for (int i = 0; i < 4; i++)
+ {
+ int h = (int)((uint)counterHash >> (i << 3));
+ int index = (h >> 1) & 15;
+ int offset = h & 1;
+ count[i] = (int)(((ulong)table[block + offset + (i << 1)] >> (index << 2)) & 0xfL);
+ }
+ return Math.Min(Math.Min(count[0], count[1]), Math.Min(count[2], count[3]));
+ }
+
+ [MethodImpl((MethodImplOptions)512)]
+ private unsafe void IncrementStd(T value)
+ {
+ var index = stackalloc int[8];
+ int blockHash = Spread(comparer.GetHashCode(value));
+ int counterHash = Rehash(blockHash);
+ int block = (blockHash & blockMask) << 3;
+
+ for (int i = 0; i < 4; i++)
+ {
+ int h = (int)((uint)counterHash >> (i << 3));
+ index[i] = (h >> 1) & 15;
+ int offset = h & 1;
+ index[i + 4] = block + offset + (i << 1);
+ }
+
+ bool added =
+ IncrementAt(index[4], index[0])
+ | IncrementAt(index[5], index[1])
+ | IncrementAt(index[6], index[2])
+ | IncrementAt(index[7], index[3]);
+
+ if (added && (++size == sampleSize))
+ {
+ Reset();
+ }
+ }
+
+ // Applies another round of hashing for additional randomization
+ private static int Rehash(int x)
+ {
+ x = (int)(x * 0x31848bab);
+ x ^= (int)((uint)x >> 14);
+ return x;
+ }
+
+ // Applies a supplemental hash functions to defends against poor quality hash.
+ private static int Spread(int x)
+ {
+ x ^= (int)((uint)x >> 17);
+ x = (int)(x * 0xed5ad4bb);
+ x ^= (int)((uint)x >> 11);
+ x = (int)(x * 0xac4c1b51);
+ x ^= (int)((uint)x >> 15);
+ return x;
+ }
+
+ private bool IncrementAt(int i, int j)
+ {
+ int offset = j << 2;
+ long mask = (0xfL << offset);
+
+ if ((table[i] & mask) != mask)
+ {
+ table[i] += (1L << offset);
+ return true;
+ }
+
+ return false;
+ }
+
+ private void Reset()
+ {
+ // unroll, almost 2x faster
+ int count0 = 0;
+ int count1 = 0;
+ int count2 = 0;
+ int count3 = 0;
+
+ for (int i = 0; i < table.Length; i += 4)
+ {
+ count0 += BitOps.BitCount(table[i] & OneMask);
+ count1 += BitOps.BitCount(table[i + 1] & OneMask);
+ count2 += BitOps.BitCount(table[i + 2] & OneMask);
+ count3 += BitOps.BitCount(table[i + 3] & OneMask);
+
+ table[i] = (long)((ulong)table[i] >> 1) & ResetMask;
+ table[i + 1] = (long)((ulong)table[i + 1] >> 1) & ResetMask;
+ table[i + 2] = (long)((ulong)table[i + 2] >> 1) & ResetMask;
+ table[i + 3] = (long)((ulong)table[i + 3] >> 1) & ResetMask;
+ }
+
+ count0 = (count0 + count1) + (count2 + count3);
+
+ size = (size - (count0 >> 2)) >> 1;
+ }
+
+#if !NETSTANDARD2_0
+ [MethodImpl((MethodImplOptions)512)]
+ private unsafe int EstimateFrequencyAvx(T value)
+ {
+ int blockHash = Spread(comparer.GetHashCode(value));
+ int counterHash = Rehash(blockHash);
+ int block = (blockHash & blockMask) << 3;
+
+ Vector128 h = Vector128.Create(counterHash);
+ h = Avx2.ShiftRightLogicalVariable(h.AsUInt32(), Vector128.Create(0U, 8U, 16U, 24U)).AsInt32();
+
+ var index = Avx2.ShiftRightLogical(h, 1);
+ index = Avx2.And(index, Vector128.Create(15)); // j - counter index
+ Vector128 offset = Avx2.And(h, Vector128.Create(1));
+ Vector128 blockOffset = Avx2.Add(Vector128.Create(block), offset); // i - table index
+ blockOffset = Avx2.Add(blockOffset, Vector128.Create(0, 2, 4, 6)); // + (i << 1)
+
+ fixed (long* tablePtr = table)
+ {
+ Vector256 tableVector = Avx2.GatherVector256(tablePtr, blockOffset, 8);
+ index = Avx2.ShiftLeftLogical(index, 2);
+
+ // convert index from int to long via permute
+ Vector256 indexLong = Vector256.Create(index, Vector128.Zero).AsInt64();
+ Vector256 permuteMask2 = Vector256.Create(0, 4, 1, 5, 2, 5, 3, 7);
+ indexLong = Avx2.PermuteVar8x32(indexLong.AsInt32(), permuteMask2).AsInt64();
+ tableVector = Avx2.ShiftRightLogicalVariable(tableVector, indexLong.AsUInt64());
+ tableVector = Avx2.And(tableVector, Vector256.Create(0xfL));
+
+ Vector256 permuteMask = Vector256.Create(0, 2, 4, 6, 1, 3, 5, 7);
+ Vector128 count = Avx2.PermuteVar8x32(tableVector.AsInt32(), permuteMask)
+ .GetLower()
+ .AsUInt16();
+
+ // set the zeroed high parts of the long value to ushort.Max
+#if NET6_0
+ count = Avx2.Blend(count, Vector128.AllBitsSet, 0b10101010);
+#else
+ count = Avx2.Blend(count, Vector128.Create(ushort.MaxValue), 0b10101010);
+#endif
+
+ return Avx2.MinHorizontal(count).GetElement(0);
+ }
+ }
+
+ [MethodImpl((MethodImplOptions)512)]
+ private unsafe void IncrementAvx(T value)
+ {
+ int blockHash = Spread(comparer.GetHashCode(value));
+ int counterHash = Rehash(blockHash);
+ int block = (blockHash & blockMask) << 3;
+
+ Vector128 h = Vector128.Create(counterHash);
+ h = Avx2.ShiftRightLogicalVariable(h.AsUInt32(), Vector128.Create(0U, 8U, 16U, 24U)).AsInt32();
+
+ Vector128 index = Avx2.ShiftRightLogical(h, 1);
+ index = Avx2.And(index, Vector128.Create(15)); // j - counter index
+ Vector128 offset = Avx2.And(h, Vector128.Create(1));
+ Vector128 blockOffset = Avx2.Add(Vector128.Create(block), offset); // i - table index
+ blockOffset = Avx2.Add(blockOffset, Vector128.Create(0, 2, 4, 6)); // + (i << 1)
+
+ fixed (long* tablePtr = table)
+ {
+ Vector256 tableVector = Avx2.GatherVector256(tablePtr, blockOffset, 8);
+
+ // j == index
+ index = Avx2.ShiftLeftLogical(index, 2);
+ Vector256 offsetLong = Vector256.Create(index, Vector128.Zero).AsInt64();
+
+ Vector256 permuteMask = Vector256.Create(0, 4, 1, 5, 2, 5, 3, 7);
+ offsetLong = Avx2.PermuteVar8x32(offsetLong.AsInt32(), permuteMask).AsInt64();
+
+ // mask = (0xfL << offset)
+ Vector256 fifteen = Vector256.Create(0xfL);
+ Vector256 mask = Avx2.ShiftLeftLogicalVariable(fifteen, offsetLong.AsUInt64());
+
+ // (table[i] & mask) != mask)
+ // Note masked is 'equal' - therefore use AndNot below
+ Vector256 masked = Avx2.CompareEqual(Avx2.And(tableVector, mask), mask);
+
+ // 1L << offset
+ Vector256 inc = Avx2.ShiftLeftLogicalVariable(Vector256.Create(1L), offsetLong.AsUInt64());
+
+ // Mask to zero out non matches (add zero below) - first operand is NOT then AND result (order matters)
+ inc = Avx2.AndNot(masked, inc);
+
+ Vector256 result = Avx2.CompareEqual(masked.AsByte(), Vector256.Zero);
+ bool wasInc = Avx2.MoveMask(result.AsByte()) == unchecked((int)(0b1111_1111_1111_1111_1111_1111_1111_1111));
+
+ tablePtr[blockOffset.GetElement(0)] += inc.GetElement(0);
+ tablePtr[blockOffset.GetElement(1)] += inc.GetElement(1);
+ tablePtr[blockOffset.GetElement(2)] += inc.GetElement(2);
+ tablePtr[blockOffset.GetElement(3)] += inc.GetElement(3);
+
+ if (wasInc && (++size == sampleSize))
+ {
+ Reset();
+ }
+ }
+ }
+#endif
+ }
+}
diff --git a/BitFaster.Caching/Lfu/ConcurrentLfuCore.cs b/BitFaster.Caching/Lfu/ConcurrentLfuCore.cs
index 0b56b09d..dc245074 100644
--- a/BitFaster.Caching/Lfu/ConcurrentLfuCore.cs
+++ b/BitFaster.Caching/Lfu/ConcurrentLfuCore.cs
@@ -258,8 +258,8 @@ public async ValueTask GetOrAddAsync(K key, Func> valu
}
}
- public bool TryGet(K key, [MaybeNullWhen(false)] out V value)
- {
+ public bool TryGet(K key, [MaybeNullWhen(false)] out V value)
+ {
return TryGetImpl(key, out value);
}
@@ -268,21 +268,21 @@ private bool TryGetImpl(K key, [MaybeNullWhen(false)] out V value)
{
if (this.dictionary.TryGetValue(key, out var node))
{
- if (!policy.IsExpired(node))
- {
- bool delayable = this.readBuffer.TryAdd(node) != BufferStatus.Full;
-
- if (this.drainStatus.ShouldDrain(delayable))
- {
- TryScheduleDrain();
- }
- value = node.Value;
+ if (!policy.IsExpired(node))
+ {
+ bool delayable = this.readBuffer.TryAdd(node) != BufferStatus.Full;
+
+ if (this.drainStatus.ShouldDrain(delayable))
+ {
+ TryScheduleDrain();
+ }
+ value = node.Value;
return true;
- }
- else
- {
- // expired case, immediately remove from the dictionary
- TryRemove(node);
+ }
+ else
+ {
+ // expired case, immediately remove from the dictionary
+ TryRemove(node);
}
}
@@ -290,25 +290,25 @@ private bool TryGetImpl(K key, [MaybeNullWhen(false)] out V value)
value = default;
return false;
- }
-
- internal bool TryGetNode(K key, [MaybeNullWhen(false)] out N node)
- {
+ }
+
+ internal bool TryGetNode(K key, [MaybeNullWhen(false)] out N node)
+ {
return this.dictionary.TryGetValue(key, out node);
}
[MethodImpl(MethodImplOptions.NoInlining)]
- void TryRemove(N node)
- {
+ void TryRemove(N node)
+ {
#if NET6_0_OR_GREATER
- if (this.dictionary.TryRemove(new KeyValuePair(node.Key, node)))
+ if (this.dictionary.TryRemove(new KeyValuePair(node.Key, node)))
#else
- // https://devblogs.microsoft.com/pfxteam/little-known-gems-atomic-conditional-removals-from-concurrentdictionary/
- if (((ICollection>)this.dictionary).Remove(new KeyValuePair(node.Key, node)))
-#endif
- {
- node.WasRemoved = true;
- AfterWrite(node);
+ // https://devblogs.microsoft.com/pfxteam/little-known-gems-atomic-conditional-removals-from-concurrentdictionary/
+ if (((ICollection>)this.dictionary).Remove(new KeyValuePair(node.Key, node)))
+#endif
+ {
+ node.WasRemoved = true;
+ AfterWrite(node);
}
}
@@ -463,6 +463,7 @@ private void ScheduleAfterWrite()
}
}
+ [MethodImpl((MethodImplOptions)512)]
private void TryScheduleDrain()
{
if (this.drainStatus.VolatileRead() >= DrainStatus.ProcessingToIdle)
@@ -521,6 +522,7 @@ internal void DrainBuffers()
}
}
+ [MethodImpl((MethodImplOptions)512)]
private bool Maintenance(N? droppedWrite = null)
{
this.drainStatus.VolatileWrite(DrainStatus.ProcessingToIdle);
@@ -577,6 +579,7 @@ private bool Maintenance(N? droppedWrite = null)
return done;
}
+ [MethodImpl((MethodImplOptions)512)]
private void OnAccess(N node)
{
// there was a cache hit even if the item was removed or is not yet added.
@@ -604,6 +607,7 @@ private void OnAccess(N node)
policy.OnRead(node);
}
+ [MethodImpl((MethodImplOptions)512)]
private void OnWrite(N node)
{
// Nodes can be removed while they are in the write buffer, in which case they should
@@ -653,6 +657,7 @@ private void OnWrite(N node)
policy.OnWrite(node);
}
+ [MethodImpl((MethodImplOptions)512)]
private void PromoteProbation(LfuNode node)
{
this.probationLru.Remove(node);
@@ -670,12 +675,14 @@ private void PromoteProbation(LfuNode node)
}
}
+ [MethodImpl((MethodImplOptions)512)]
private void EvictEntries()
{
var candidate = EvictFromWindow();
EvictFromMain(candidate);
}
+ [MethodImpl((MethodImplOptions)512)]
private LfuNode EvictFromWindow()
{
LfuNode? first = null;
@@ -720,6 +727,7 @@ public void Next()
}
}
+ [MethodImpl((MethodImplOptions)512)]
private void EvictFromMain(LfuNode candidateNode)
{
var victim = new EvictIterator(this.cmSketch, this.probationLru.First); // victims are LRU position in probation
@@ -795,6 +803,7 @@ private void EvictFromMain(LfuNode candidateNode)
}
}
+ [MethodImpl((MethodImplOptions)512)]
private bool AdmitCandidate(K candidateKey, K victimKey)
{
int victimFreq = this.cmSketch.EstimateFrequency(victimKey);