From 7b53e0cc967058cb1e32e16b8ef1193a4674ee57 Mon Sep 17 00:00:00 2001 From: Alex Peck Date: Sat, 16 Nov 2024 21:13:12 -0800 Subject: [PATCH 01/10] net9 --- BitFaster.Caching/BitFaster.Caching.csproj | 4 ++-- BitFaster.Caching/Lfu/ConcurrentLfuCore.cs | 17 +++++++++++++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/BitFaster.Caching/BitFaster.Caching.csproj b/BitFaster.Caching/BitFaster.Caching.csproj index 20405be8..3111b210 100644 --- a/BitFaster.Caching/BitFaster.Caching.csproj +++ b/BitFaster.Caching/BitFaster.Caching.csproj @@ -1,8 +1,8 @@ - netstandard2.0;netcoreapp3.1;net6.0 - 10.0 + netstandard2.0;netcoreapp3.1;net6.0;net9.0 + 13.0 Alex Peck BitFaster.Caching diff --git a/BitFaster.Caching/Lfu/ConcurrentLfuCore.cs b/BitFaster.Caching/Lfu/ConcurrentLfuCore.cs index 67f6dbc1..2eb5d4f3 100644 --- a/BitFaster.Caching/Lfu/ConcurrentLfuCore.cs +++ b/BitFaster.Caching/Lfu/ConcurrentLfuCore.cs @@ -64,8 +64,12 @@ internal struct ConcurrentLfuCore : IBoundedPolicy private readonly LfuCapacityPartition capacity; internal readonly DrainStatus drainStatus = new(); - private readonly object maintenanceLock = new(); +#if NET9_0_OR_GREATER + private readonly Lock maintenanceLock = new(); +#else + private readonly object maintenanceLock = new(); +#endif private readonly IScheduler scheduler; private readonly Action drainBuffers; @@ -484,7 +488,12 @@ private void TryScheduleDrain() bool lockTaken = false; try { +#if NET9_0_OR_GREATER + lockTaken = maintenanceLock.TryEnter(); +#else Monitor.TryEnter(maintenanceLock, ref lockTaken); +#endif + if (lockTaken) { @@ -503,7 +512,11 @@ private void TryScheduleDrain() { if (lockTaken) { - Monitor.Exit(maintenanceLock); +#if NET9_0_OR_GREATER + maintenanceLock.Exit(); +#else + Monitor.Exit(maintenanceLock); +#endif } } } From fcb81452fade0e77105b67b755a619d7adfd3edb Mon Sep 17 00:00:00 2001 From: Alex Peck Date: Sat, 16 Nov 2024 21:17:09 -0800 Subject: [PATCH 02/10] build scripts --- .github/workflows/benchpr.yml | 3 +++ .github/workflows/gate.yml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/.github/workflows/benchpr.yml b/.github/workflows/benchpr.yml index 38bd01f5..4c2cdd2e 100644 --- a/.github/workflows/benchpr.yml +++ b/.github/workflows/benchpr.yml @@ -25,6 +25,7 @@ jobs: dotnet-version: | 6.0.x 8.0.x + 9.0.x - name: Install dependencies run: dotnet restore - name: Build @@ -60,6 +61,7 @@ jobs: dotnet-version: | 6.0.x 8.0.x + 9.0.x - name: Install dependencies run: dotnet restore - name: Build @@ -84,6 +86,7 @@ jobs: dotnet-version: | 6.0.x 8.0.x + 9.0.x - name: Install dependencies run: dotnet restore - name: Build diff --git a/.github/workflows/gate.yml b/.github/workflows/gate.yml index 7d8d3082..2cac34d6 100644 --- a/.github/workflows/gate.yml +++ b/.github/workflows/gate.yml @@ -24,6 +24,7 @@ jobs: 3.1.x 6.0.x 8.0.x + 9.0.x - name: Install dependencies run: dotnet restore - name: Build @@ -98,6 +99,7 @@ jobs: dotnet-version: | 6.0.x 8.0.x + 9.0.x - name: Install dependencies run: dotnet restore - name: Build @@ -139,6 +141,7 @@ jobs: dotnet-version: | 6.0.x 8.0.x + 9.0.x - name: Install dependencies run: dotnet restore - name: Build From c52bc232bccf93871e1a51588c6230280a23742a Mon Sep 17 00:00:00 2001 From: Alex Peck Date: Sat, 16 Nov 2024 21:18:51 -0800 Subject: [PATCH 03/10] infer --- .github/workflows/infer.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/infer.yml b/.github/workflows/infer.yml index 5782bdc2..8b139041 100644 --- a/.github/workflows/infer.yml +++ b/.github/workflows/infer.yml @@ -18,7 +18,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v2 with: - dotnet-version: 8.0.x + dotnet-version: 9.0.x - name: Install dependencies run: dotnet restore - name: Build From 291a9f46798612ea995bf0dfcb31f8eb79873719 Mon Sep 17 00:00:00 2001 From: Alex Peck Date: Mon, 18 Nov 2024 12:40:01 -0800 Subject: [PATCH 04/10] lock bench --- .../BitFaster.Caching.Benchmarks.csproj | 2 +- BitFaster.Caching.Benchmarks/LockBench.cs | 61 +++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 BitFaster.Caching.Benchmarks/LockBench.cs diff --git a/BitFaster.Caching.Benchmarks/BitFaster.Caching.Benchmarks.csproj b/BitFaster.Caching.Benchmarks/BitFaster.Caching.Benchmarks.csproj index 1267e6b3..d7048819 100644 --- a/BitFaster.Caching.Benchmarks/BitFaster.Caching.Benchmarks.csproj +++ b/BitFaster.Caching.Benchmarks/BitFaster.Caching.Benchmarks.csproj @@ -2,7 +2,7 @@ Exe - net48;net6.0;net8.0 + net48;net6.0;net8.0;net9.0 True true diff --git a/BitFaster.Caching.Benchmarks/LockBench.cs b/BitFaster.Caching.Benchmarks/LockBench.cs new file mode 100644 index 00000000..3443fa9a --- /dev/null +++ b/BitFaster.Caching.Benchmarks/LockBench.cs @@ -0,0 +1,61 @@ + +using System.Threading; +using Benchly; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Jobs; + +namespace BitFaster.Caching.Benchmarks +{ + [SimpleJob(RuntimeMoniker.Net90)] + [MemoryDiagnoser(displayGenColumns: false)] + [HideColumns("Job", "Median", "RatioSD", "Alloc Ratio")] + [ColumnChart(Title ="Try enter ({JOB})")] + public class LockBench + { + private int _value; + private readonly object monitorLock = new object(); +#if NET9_0_OR_GREATER + private readonly Lock threadingLock = new Lock(); +#endif + + [Benchmark(Baseline = true)] + public void UseMonitor() + { + bool lockTaken = false; + Monitor.TryEnter(monitorLock, ref lockTaken); + + if (lockTaken) + { + try + { + _value++; + } + finally + { + if (lockTaken) + { + Monitor.Exit(monitorLock); + } + } + } + } + + [Benchmark()] + public void UseLock() + { +#if NET9_0_OR_GREATER + if (threadingLock.TryEnter()) + { + try + { + _value++; + } + finally + { + threadingLock.Exit(); + } + } +#endif + } + } +} From a1484c47de5565eb2876b0aeaa740c9d6e4078d1 Mon Sep 17 00:00:00 2001 From: Alex Peck Date: Mon, 18 Nov 2024 12:46:56 -0800 Subject: [PATCH 05/10] simpler lock entry --- BitFaster.Caching/Lfu/ConcurrentLfuCore.cs | 30 ++++++++++------------ 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/BitFaster.Caching/Lfu/ConcurrentLfuCore.cs b/BitFaster.Caching/Lfu/ConcurrentLfuCore.cs index 2eb5d4f3..22c10c1a 100644 --- a/BitFaster.Caching/Lfu/ConcurrentLfuCore.cs +++ b/BitFaster.Caching/Lfu/ConcurrentLfuCore.cs @@ -485,17 +485,15 @@ private void TryScheduleDrain() return; } - bool lockTaken = false; - try - { #if NET9_0_OR_GREATER - lockTaken = maintenanceLock.TryEnter(); + if (maintenanceLock.TryEnter()) #else - Monitor.TryEnter(maintenanceLock, ref lockTaken); + bool lockTaken = false; + Monitor.TryEnter(maintenanceLock, ref lockTaken); + if (lockTaken) #endif - - - if (lockTaken) + { + try { int status = this.drainStatus.NonVolatileRead(); @@ -507,16 +505,16 @@ private void TryScheduleDrain() this.drainStatus.VolatileWrite(DrainStatus.ProcessingToIdle); scheduler.Run(this.drainBuffers); } - } - finally - { - if (lockTaken) - { + finally + { #if NET9_0_OR_GREATER - maintenanceLock.Exit(); + maintenanceLock.Exit(); #else - Monitor.Exit(maintenanceLock); -#endif + if (lockTaken) + { + Monitor.Exit(maintenanceLock); + } +#endif } } } From 1abc7f767c804e50968f2b3253cf54093b75f9f2 Mon Sep 17 00:00:00 2001 From: Alex Peck Date: Tue, 19 Nov 2024 19:44:42 -0800 Subject: [PATCH 06/10] codeql --- .github/workflows/codeql-analysis.yml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index b076dc1a..1b83c3f6 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -52,8 +52,8 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v2 + #- name: Autobuild + # uses: github/codeql-action/autobuild@v2 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -66,5 +66,16 @@ jobs: # make bootstrap # make release + - name: Setup .NET SDK + uses: actions/setup-dotnet@v3.2.0 + with: + dotnet-version: '9.0.x' + + - name: Restore dependencies + run: dotnet restore + + - name: Build + run: dotnet build --configuration Debug --no-restore + - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v2 From dd6328abaaddd99bc44655d15974069ee7a8918a Mon Sep 17 00:00:00 2001 From: Alex Peck Date: Wed, 20 Nov 2024 18:01:44 -0800 Subject: [PATCH 07/10] alt outline --- BitFaster.Caching/Lru/ConcurrentLruCore.cs | 145 +++++++++++++++++++++ BitFaster.Caching/Throw.cs | 3 + 2 files changed, 148 insertions(+) diff --git a/BitFaster.Caching/Lru/ConcurrentLruCore.cs b/BitFaster.Caching/Lru/ConcurrentLruCore.cs index 937d6747..d126ee31 100644 --- a/BitFaster.Caching/Lru/ConcurrentLruCore.cs +++ b/BitFaster.Caching/Lru/ConcurrentLruCore.cs @@ -850,6 +850,151 @@ private static Optional> CreateEvents(ConcurrentLruCore(ConcurrentDictionary d) + where TAlternateKey : notnull, allows ref struct + { + return d.Comparer is IAlternateEqualityComparer; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static IAlternateEqualityComparer GetAlternateComparer(ConcurrentDictionary d) + where TAlternateKey : notnull, allows ref struct + { + Debug.Assert(IsCompatibleKey(d)); + return Unsafe.As>(d.Comparer!); + } + + public IAlternateCache GetAlternateCache() where TAlternateKey : notnull, allows ref struct + { + if (!IsCompatibleKey(this.dictionary)) + { + Throw.IncompatibleComparer(); + } + + return new AlternateCache(this); + } + + public bool TryGetAlternateCache([MaybeNullWhen(false)] out IAlternateCache lookup) where TAlternateKey : notnull, allows ref struct + { + if (IsCompatibleKey(this.dictionary)) + { + lookup = new AlternateCache(this); + return true; + } + + lookup = default; + return false; + } + + // Rough idea of alternate cache interface + // Note: we need a sync and async variant, plumbed into ICache and IAsyncCache. + public interface IAlternateCache where TAlternateKey : notnull, allows ref struct + { + bool TryGet(TAlternateKey key, [MaybeNullWhen(false)] out V value); + + bool TryRemove(TAlternateKey key, [MaybeNullWhen(false)] out K actualKey, [MaybeNullWhen(false)] out V value); + + V GetOrAdd(TAlternateKey altKey, Func valueFactory); + + V GetOrAdd(TAlternateKey altKey, Func valueFactory, TArg factoryArgument); + + // TryUpdate + // AddOrUpdate + } + + internal readonly struct AlternateCache : IAlternateCache where TAlternateKey : notnull, allows ref struct + { + /// Initialize the instance. The dictionary must have already been verified to have a compatible comparer. + internal AlternateCache(ConcurrentLruCore lru) + { + Debug.Assert(lru is not null); + Debug.Assert(IsCompatibleKey(lru.dictionary)); + Lru = lru; + } + + internal ConcurrentLruCore Lru { get; } + + public bool TryGet(TAlternateKey key, [MaybeNullWhen(false)] out V value) + { + var alternate = this.Lru.dictionary.GetAlternateLookup(); + + if (alternate.TryGetValue(key, out var item)) + { + return Lru.GetOrDiscard(item, out value); + } + + value = default; + Lru.telemetryPolicy.IncrementMiss(); + return false; + } + + public bool TryRemove(TAlternateKey key, [MaybeNullWhen(false)] out K actualKey, [MaybeNullWhen(false)] out V value) + { + var alternate = this.Lru.dictionary.GetAlternateLookup(); + + if (alternate.TryGetValue(key, out var item)) + { + Lru.OnRemove(item.Key, item, ItemRemovedReason.Removed); + actualKey = item.Key; + value = item.Value; + return true; + } + + actualKey = default; + value = default; + return false; + } + + public V GetOrAdd(TAlternateKey altKey, Func valueFactory) + { + var alternate = this.Lru.dictionary.GetAlternateLookup(); + + while (true) + { + if (alternate.TryGetValue(altKey, out var item)) + { + return item.Value; + } + + // We cannot avoid allocating the key since it is required for item policy etc. Thus fall back to Lru for add. + // The value factory may be called concurrently for the same key, but the first write to the dictionary wins. + K key = GetAlternateComparer(this.Lru.dictionary).Create(altKey); + V value = valueFactory(altKey); + if (Lru.TryAdd(key, value)) + { + return value; + } + } + } + + public V GetOrAdd(TAlternateKey altKey, Func valueFactory, TArg factoryArgument) + { + var alternate = this.Lru.dictionary.GetAlternateLookup(); + + while (true) + { + if (alternate.TryGetValue(altKey, out var item)) + { + return item.Value; + } + + // We cannot avoid allocating the key since it is required for item policy etc. Thus fall back to Lru for add. + // The value factory may be called concurrently for the same key, but the first write to the dictionary wins. + K key = GetAlternateComparer(this.Lru.dictionary).Create(altKey); + V value = valueFactory(altKey, factoryArgument); + if (Lru.TryAdd(key, value)) + { + return value; + } + } + } + } + +#endif + // To get JIT optimizations, policies must be structs. // If the structs are returned directly via properties, they will be copied. Since // telemetryPolicy is a mutable struct, copy is bad. One workaround is to store the diff --git a/BitFaster.Caching/Throw.cs b/BitFaster.Caching/Throw.cs index 9870e697..b33bf705 100644 --- a/BitFaster.Caching/Throw.cs +++ b/BitFaster.Caching/Throw.cs @@ -26,6 +26,9 @@ internal static class Throw [DoesNotReturn] public static void Disposed() => throw CreateObjectDisposedException(); + [DoesNotReturn] + public static void IncompatibleComparer() => throw new InvalidOperationException("Incompatible comparer"); + [MethodImpl(MethodImplOptions.NoInlining)] private static ArgumentNullException CreateArgumentNullException(ExceptionArgument arg) => new ArgumentNullException(GetArgumentString(arg)); From 1a018a8a48ba61bae0ac5768b3b829fc90cd9b99 Mon Sep 17 00:00:00 2001 From: Alex Peck Date: Sun, 12 Jan 2025 20:32:29 -0800 Subject: [PATCH 08/10] fix proj --- BitFaster.Caching/BitFaster.Caching.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BitFaster.Caching/BitFaster.Caching.csproj b/BitFaster.Caching/BitFaster.Caching.csproj index 74a40544..3d7b1e68 100644 --- a/BitFaster.Caching/BitFaster.Caching.csproj +++ b/BitFaster.Caching/BitFaster.Caching.csproj @@ -10,7 +10,7 @@ LICENSE ReadMe.md true - 2.5.2 + 2.5.3 Copyright © Alex Peck $([System.DateTime]::Now.ToString(yyyy)) https://github.com/bitfaster/BitFaster.Caching From 07d4369594fb3d56c53910e0e3fc3896f84ec28f Mon Sep 17 00:00:00 2001 From: Alex Peck Date: Sun, 12 Jan 2025 20:35:38 -0800 Subject: [PATCH 09/10] fix build --- BitFaster.Caching/Intrinsics.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BitFaster.Caching/Intrinsics.cs b/BitFaster.Caching/Intrinsics.cs index 45908a01..a9104823 100644 --- a/BitFaster.Caching/Intrinsics.cs +++ b/BitFaster.Caching/Intrinsics.cs @@ -2,7 +2,7 @@ using System.Runtime.Intrinsics.X86; #endif -#if NET6_0 +#if NET6_0_OR_GREATER using System.Runtime.Intrinsics.Arm; #endif From f43c1c94e5e78ded68dd4a14c5cd0fd9df685274 Mon Sep 17 00:00:00 2001 From: Alex Peck Date: Fri, 14 Feb 2025 19:54:30 -0800 Subject: [PATCH 10/10] fix merge --- .github/workflows/codeql-analysis.yml | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 22b7a185..c2fd46b1 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -38,11 +38,11 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -52,8 +52,8 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v2 + #- name: Autobuild + # uses: github/codeql-action/autobuild@v3 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -65,6 +65,16 @@ jobs: #- run: | # make bootstrap # make release + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '9.0.x' + + - name: Restore dependencies + run: dotnet restore + + - name: Build + run: dotnet build --configuration Debug --no-restore - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3