diff --git a/docs/Performance.md b/docs/Performance.md index b741ff8d..65896ab5 100644 --- a/docs/Performance.md +++ b/docs/Performance.md @@ -21,18 +21,18 @@ Job=Core Runtime=Core | Method | Mean [ns] | Error [ns] | StdDev [ns] | Gen 0 | Gen 1 | Gen 2 | Allocated [B] | |-------------------------- |----------------:|--------------:|--------------:|-------:|------:|------:|--------------:| -| SetupAndTeardown | 284.8 ns | 2.39 ns | 2.12 ns | 0.1326 | - | - | 416 B | -| Set | 700.3 ns | 16.63 ns | 17.79 ns | 0.2108 | - | - | 664 B | -| Set_TwoLayers | 748.5 ns | 5.08 ns | 4.75 ns | 0.2804 | - | - | 880 B | -| Evict | 765.4 ns | 4.47 ns | 4.18 ns | 0.2108 | - | - | 664 B | -| Evict_TwoLayers | 923.5 ns | 3.78 ns | 3.54 ns | 0.2804 | - | - | 880 B | -| Cleanup | 981.1 ns | 6.33 ns | 5.61 ns | 0.2098 | - | - | 664 B | -| Cleanup_TwoLayers | 1,189.7 ns | 15.05 ns | 13.34 ns | 0.2804 | - | - | 880 B | -| GetMiss | 393.9 ns | 3.29 ns | 3.08 ns | 0.1326 | - | - | 416 B | -| GetHit | 817.5 ns | 8.24 ns | 6.88 ns | 0.2346 | - | - | 736 B | -| GetOrSet | 2,004.4 ns | 20.28 ns | 18.97 ns | 0.4768 | - | - | 1504 B | -| GetOrSet_TwoSimultaneous | 62,133,010.3 ns | 415,745.68 ns | 368,547.72 ns | - | - | - | 2720 B | -| GetOrSet_FourSimultaneous | 62,121,809.2 ns | 478,958.15 ns | 448,017.75 ns | - | - | - | 4565 B | +| SetupAndTeardown | 275.0 ns | 1.30 ns | 1.22 ns | 0.1326 | - | - | 416 B | +| Set | 678.8 ns | 3.35 ns | 3.13 ns | 0.1879 | - | - | 592 B | +| Set_TwoLayers | 773.4 ns | 2.06 ns | 1.93 ns | 0.2575 | - | - | 808 B | +| Evict | 790.9 ns | 3.48 ns | 3.25 ns | 0.1879 | - | - | 592 B | +| Evict_TwoLayers | 941.2 ns | 4.91 ns | 4.60 ns | 0.2575 | - | - | 808 B | +| Cleanup | 974.4 ns | 7.65 ns | 7.16 ns | 0.1869 | - | - | 592 B | +| Cleanup_TwoLayers | 1,208.6 ns | 4.53 ns | 3.54 ns | 0.2575 | - | - | 808 B | +| GetMiss | 388.1 ns | 2.34 ns | 2.07 ns | 0.1326 | - | - | 416 B | +| GetHit | 800.7 ns | 5.10 ns | 4.52 ns | 0.1879 | - | - | 592 B | +| GetOrSet | 2,043.9 ns | 14.51 ns | 13.58 ns | 0.3357 | - | - | 1064 B | +| GetOrSet_TwoSimultaneous | 62,232,054.8 ns | 329,492.60 ns | 308,207.58 ns | - | - | - | 3325 B | +| GetOrSet_FourSimultaneous | 62,144,815.6 ns | 395,286.63 ns | 369,751.36 ns | - | - | - | 3677 B | ## Cache Layer Comparison Benchmark diff --git a/src/CacheTower.Extensions.Redis/RedisLockExtension.cs b/src/CacheTower.Extensions.Redis/RedisLockExtension.cs index 912786ac..ff03ee67 100644 --- a/src/CacheTower.Extensions.Redis/RedisLockExtension.cs +++ b/src/CacheTower.Extensions.Redis/RedisLockExtension.cs @@ -59,7 +59,7 @@ public void Register(ICacheStack cacheStack) RegisteredStack = cacheStack; } - public async Task> RefreshValueAsync(string cacheKey, Func>> valueProvider, CacheSettings settings) + public async ValueTask> RefreshValueAsync(string cacheKey, Func>> valueProvider, CacheSettings settings) { var hasLock = await Database.StringSetAsync(cacheKey, RedisValue.EmptyString, expiry: LockTimeout, when: When.NotExists); diff --git a/src/CacheTower.Extensions.Redis/RedisRemoteEvictionExtension.cs b/src/CacheTower.Extensions.Redis/RedisRemoteEvictionExtension.cs index 788ffe3f..cd03e211 100644 --- a/src/CacheTower.Extensions.Redis/RedisRemoteEvictionExtension.cs +++ b/src/CacheTower.Extensions.Redis/RedisRemoteEvictionExtension.cs @@ -34,7 +34,7 @@ public RedisRemoteEvictionExtension(ConnectionMultiplexer connection, string cha FlaggedRefreshes = new HashSet(StringComparer.Ordinal); } - public async Task OnValueRefreshAsync(string cacheKey, TimeSpan timeToLive) + public async ValueTask OnValueRefreshAsync(string cacheKey, TimeSpan timeToLive) { lock (FlaggedRefreshesLockObj) { diff --git a/src/CacheTower/CacheStack.cs b/src/CacheTower/CacheStack.cs index 11a64469..d48a697c 100644 --- a/src/CacheTower/CacheStack.cs +++ b/src/CacheTower/CacheStack.cs @@ -52,7 +52,7 @@ private void ThrowIfDisposed() } } - public async Task CleanupAsync() + public async ValueTask CleanupAsync() { ThrowIfDisposed(); @@ -70,7 +70,7 @@ public async Task CleanupAsync() } } - public async Task EvictAsync(string cacheKey) + public async ValueTask EvictAsync(string cacheKey) { ThrowIfDisposed(); @@ -93,7 +93,7 @@ public async Task EvictAsync(string cacheKey) } } - public async Task> SetAsync(string cacheKey, T value, TimeSpan timeToLive) + public async ValueTask> SetAsync(string cacheKey, T value, TimeSpan timeToLive) { ThrowIfDisposed(); @@ -102,7 +102,7 @@ public async Task> SetAsync(string cacheKey, T value, TimeSpan return cacheEntry; } - public async Task SetAsync(string cacheKey, CacheEntry cacheEntry) + public async ValueTask SetAsync(string cacheKey, CacheEntry cacheEntry) { ThrowIfDisposed(); @@ -116,7 +116,7 @@ public async Task SetAsync(string cacheKey, CacheEntry cacheEntry) throw new ArgumentNullException(nameof(cacheEntry)); } - for (int i = 0, l = CacheLayers.Length; i < l; i += 2) + for (int i = 0, l = CacheLayers.Length; i < l; i++) { var layer = CacheLayers[i]; if (layer is ISyncCacheLayer syncLayerOne) @@ -130,7 +130,7 @@ public async Task SetAsync(string cacheKey, CacheEntry cacheEntry) } } - public async Task> GetAsync(string cacheKey) + public async ValueTask> GetAsync(string cacheKey) { ThrowIfDisposed(); @@ -171,7 +171,7 @@ public async Task> GetAsync(string cacheKey) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private async Task<(int LayerIndex, CacheEntry CacheEntry)> GetWithLayerIndexAsync(string cacheKey) + private async ValueTask<(int LayerIndex, CacheEntry CacheEntry)> GetWithLayerIndexAsync(string cacheKey) { for (var layerIndex = 0; layerIndex < CacheLayers.Length; layerIndex++) { @@ -204,7 +204,7 @@ public async Task> GetAsync(string cacheKey) return default; } - public async Task GetOrSetAsync(string cacheKey, Func> getter, CacheSettings settings) + public async ValueTask GetOrSetAsync(string cacheKey, Func> getter, CacheSettings settings) { ThrowIfDisposed(); @@ -256,7 +256,7 @@ public async Task GetOrSetAsync(string cacheKey, Func(int fromIndexExclusive, string cacheKey, CacheEntry cacheEntry) + private async ValueTask BackPopulateCacheAsync(int fromIndexExclusive, string cacheKey, CacheEntry cacheEntry) { ThrowIfDisposed(); @@ -305,7 +305,7 @@ private async Task BackPopulateCacheAsync(int fromIndexExclusive, string cach } } - private async Task> RefreshValueAsync(string cacheKey, Func> getter, CacheSettings settings, bool waitForRefresh) + private async ValueTask> RefreshValueAsync(string cacheKey, Func> getter, CacheSettings settings, bool waitForRefresh) { ThrowIfDisposed(); diff --git a/src/CacheTower/CacheTower.csproj b/src/CacheTower/CacheTower.csproj index 979c49e0..545c8cdb 100644 --- a/src/CacheTower/CacheTower.csproj +++ b/src/CacheTower/CacheTower.csproj @@ -12,6 +12,7 @@ + diff --git a/src/CacheTower/Extensions/ExtensionContainer.cs b/src/CacheTower/Extensions/ExtensionContainer.cs index 743d4ae5..345130a0 100644 --- a/src/CacheTower/Extensions/ExtensionContainer.cs +++ b/src/CacheTower/Extensions/ExtensionContainer.cs @@ -63,7 +63,7 @@ public void Register(ICacheStack cacheStack) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Task> RefreshValueAsync(string cacheKey, Func>> valueProvider, CacheSettings settings) + public ValueTask> RefreshValueAsync(string cacheKey, Func>> valueProvider, CacheSettings settings) { if (!HasRefreshWrapperExtension) { @@ -76,7 +76,7 @@ public Task> RefreshValueAsync(string cacheKey, Func> RefreshValueAsync(string cacheKey, Func>> valueProvider, CacheSettings settings); + ValueTask> RefreshValueAsync(string cacheKey, Func>> valueProvider, CacheSettings settings); } } diff --git a/src/CacheTower/ICacheStack.cs b/src/CacheTower/ICacheStack.cs index f3973332..d2f854ac 100644 --- a/src/CacheTower/ICacheStack.cs +++ b/src/CacheTower/ICacheStack.cs @@ -7,11 +7,11 @@ namespace CacheTower { public interface ICacheStack { - Task CleanupAsync(); - Task EvictAsync(string cacheKey); - Task> SetAsync(string cacheKey, T value, TimeSpan timeToLive); - Task SetAsync(string cacheKey, CacheEntry cacheEntry); - Task> GetAsync(string cacheKey); - Task GetOrSetAsync(string cacheKey, Func> getter, CacheSettings settings); + ValueTask CleanupAsync(); + ValueTask EvictAsync(string cacheKey); + ValueTask> SetAsync(string cacheKey, T value, TimeSpan timeToLive); + ValueTask SetAsync(string cacheKey, CacheEntry cacheEntry); + ValueTask> GetAsync(string cacheKey); + ValueTask GetOrSetAsync(string cacheKey, Func> getter, CacheSettings settings); } } diff --git a/tests/CacheTower.Benchmarks/CacheStackBenchmark.cs b/tests/CacheTower.Benchmarks/CacheStackBenchmark.cs index 7bc3aec8..a7c146d4 100644 --- a/tests/CacheTower.Benchmarks/CacheStackBenchmark.cs +++ b/tests/CacheTower.Benchmarks/CacheStackBenchmark.cs @@ -134,7 +134,8 @@ public async Task GetOrSet_TwoSimultaneous() return 12; }, new CacheSettings(TimeSpan.FromDays(1))); - await Task.WhenAll(task1, task2); + await task1; + await task2; } } [Benchmark] @@ -164,7 +165,10 @@ public async Task GetOrSet_FourSimultaneous() return 12; }, new CacheSettings(TimeSpan.FromDays(1))); - await Task.WhenAll(task1, task2, task3, task4); + await task1; + await task2; + await task3; + await task4; } } } diff --git a/tests/CacheTower.Benchmarks/Extensions/BaseRefreshWrapperExtensionsBenchmark.cs b/tests/CacheTower.Benchmarks/Extensions/BaseRefreshWrapperExtensionsBenchmark.cs index 92cb0d8f..3431b0a5 100644 --- a/tests/CacheTower.Benchmarks/Extensions/BaseRefreshWrapperExtensionsBenchmark.cs +++ b/tests/CacheTower.Benchmarks/Extensions/BaseRefreshWrapperExtensionsBenchmark.cs @@ -15,7 +15,7 @@ public async Task RefreshValue() extension.Register(CacheStack); await extension.RefreshValueAsync("RefreshValue", () => { - return Task.FromResult(new CacheEntry(5, DateTime.UtcNow, TimeSpan.FromDays(1))); + return new ValueTask>(new CacheEntry(5, DateTime.UtcNow, TimeSpan.FromDays(1))); }, new CacheSettings(TimeSpan.FromDays(1))); await DisposeOf(extension); } diff --git a/tests/CacheTower.Tests/CacheStackStressTests.cs b/tests/CacheTower.Tests/CacheStackStressTests.cs index e747b74a..027307fa 100644 --- a/tests/CacheTower.Tests/CacheStackStressTests.cs +++ b/tests/CacheTower.Tests/CacheStackStressTests.cs @@ -31,7 +31,7 @@ public async Task SimulatenousGetOrSet_CacheMiss(int iterations) return index; }, new CacheSettings(TimeSpan.FromDays(1))); - allTasks.Add(iterationTask); + allTasks.Add(iterationTask.AsTask()); } if (stopwatch.Elapsed.TotalSeconds > 10) @@ -75,7 +75,7 @@ public async Task SimulatenousGetOrSet_CacheMiss_UniqueKeys(int iterations) return index + 1; }, new CacheSettings(TimeSpan.FromDays(1))); - allTasks.Add(iterationTask); + allTasks.Add(iterationTask.AsTask()); } var lastTaskResult = await allTasks[iterations - 1]; diff --git a/tests/CacheTower.Tests/CacheStackTests.cs b/tests/CacheTower.Tests/CacheStackTests.cs index df3a54ed..3364f079 100644 --- a/tests/CacheTower.Tests/CacheStackTests.cs +++ b/tests/CacheTower.Tests/CacheStackTests.cs @@ -233,7 +233,7 @@ public async Task GetOrSet_ConcurrentStaleCacheHits() var cacheEntry = new CacheEntry(23, DateTime.UtcNow.AddDays(-3), TimeSpan.FromDays(1)); await cacheStack.SetAsync("GetOrSet_ConcurrentStaleCacheHits", cacheEntry); - Task DoRequest() + ValueTask DoRequest() { return cacheStack.GetOrSetAsync("GetOrSet_ConcurrentStaleCacheHits", async (oldValue, context) => { diff --git a/tests/CacheTower.Tests/Extensions/ExtensionContainerTests.cs b/tests/CacheTower.Tests/Extensions/ExtensionContainerTests.cs index 30e2428c..cf758774 100644 --- a/tests/CacheTower.Tests/Extensions/ExtensionContainerTests.cs +++ b/tests/CacheTower.Tests/Extensions/ExtensionContainerTests.cs @@ -39,13 +39,13 @@ public async Task RefreshWrapperExtension() var refreshedValue = await container.RefreshValueAsync("WrapperTestCacheKey", () => { - return Task.FromResult(cacheEntry); + return new ValueTask>(cacheEntry); }, new CacheSettings(TimeSpan.FromDays(1))); refreshWrapperMock.Verify(e => e.Register(cacheStackMock.Object), Times.Once); refreshWrapperMock.Verify(e => e.RefreshValueAsync( "WrapperTestCacheKey", - It.IsAny>>>(), new CacheSettings(TimeSpan.FromDays(1)) + It.IsAny>>>(), new CacheSettings(TimeSpan.FromDays(1)) ), Times.Once ); diff --git a/tests/CacheTower.Tests/Extensions/Redis/RedisLockExtensionTests.cs b/tests/CacheTower.Tests/Extensions/Redis/RedisLockExtensionTests.cs index f66dae0f..9b3fc13e 100644 --- a/tests/CacheTower.Tests/Extensions/Redis/RedisLockExtensionTests.cs +++ b/tests/CacheTower.Tests/Extensions/Redis/RedisLockExtensionTests.cs @@ -97,8 +97,7 @@ await connection.GetSubscriber().SubscribeAsync("CacheTower.CacheLock", (channel var cacheEntry = new CacheEntry(13, DateTime.UtcNow, TimeSpan.FromDays(1)); await extension.RefreshValueAsync("TestKey", - () => Task.FromResult(cacheEntry), new CacheSettings(TimeSpan.FromDays(1))); - + () => new ValueTask>(cacheEntry), new CacheSettings(TimeSpan.FromDays(1))); var waitTask = taskCompletionSource.Task; await Task.WhenAny(waitTask, Task.Delay(TimeSpan.FromSeconds(10))); @@ -132,17 +131,17 @@ public async Task WaitingTaskInSameInstanceUnlocksAndCompletes() return cacheEntry; }, new CacheSettings(TimeSpan.FromDays(1)) - ); + ).AsTask(); await secondaryTaskKickoff.Task; var secondaryTask = extension.RefreshValueAsync("TestKey", () => { - return Task.FromResult(cacheEntry); + return new ValueTask>(cacheEntry); }, new CacheSettings(TimeSpan.FromDays(1)) - ); + ).AsTask(); var succeedingTask = await Task.WhenAny(primaryTask, secondaryTask); Assert.AreEqual(await primaryTask, await succeedingTask, "Processing task call didn't complete first - something has gone very wrong."); @@ -178,17 +177,17 @@ public async Task WaitingTaskInDifferentInstanceUnlocksAndCompletes() return cacheEntry; }, new CacheSettings(TimeSpan.FromDays(1)) - ); + ).AsTask(); await secondaryTaskKickoff.Task; var secondaryTask = extensionTwo.RefreshValueAsync("TestKey", () => { - return Task.FromResult(cacheEntry); + return new ValueTask>(cacheEntry); }, new CacheSettings(TimeSpan.FromDays(1)) - ); + ).AsTask(); var succeedingTask = await Task.WhenAny(primaryTask, secondaryTask); Assert.AreEqual(await primaryTask, await succeedingTask, "Processing task call didn't complete first - something has gone very wrong.");