Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions BlitzCache.Benchmarks/BenchmarkConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Toolchains.InProcess.Emit;

namespace BlitzCache.Benchmarks;

/// <summary>
/// Custom benchmark configuration for development and testing.
/// </summary>
public class DebugBenchmarkConfig : ManualConfig
{
public DebugBenchmarkConfig()
{
AddJob(Job.Default.WithToolchain(InProcessEmitToolchain.Instance));
WithOptions(ConfigOptions.DisableOptimizationsValidator);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
```

BenchmarkDotNet v0.14.0, Ubuntu 24.04.3 LTS (Noble Numbat)
AMD EPYC 7763, 1 CPU, 4 logical and 2 physical cores
.NET SDK 8.0.119
[Host] : .NET 8.0.19 (8.0.1925.36514), X64 RyuJIT AVX2
DefaultJob : .NET 8.0.19 (8.0.1925.36514), X64 RyuJIT AVX2


```
| Method | Mean | Error | StdDev | Ratio | Gen0 | Gen1 | Allocated | Alloc Ratio |
|------------------------------ |----------:|----------:|----------:|------:|-------:|-------:|----------:|------------:|
| BlitzCache_ConcurrentSameKey | 12.160 μs | 0.0652 μs | 0.0610 μs | 1.00 | 1.4801 | 0.0305 | 24.38 KB | 1.00 |
| MemoryCache_ConcurrentSameKey | 7.024 μs | 0.0307 μs | 0.0272 μs | 0.58 | 0.5798 | 0.0153 | 9.53 KB | 0.39 |
| LazyCache_ConcurrentSameKey | 21.557 μs | 0.1140 μs | 0.0890 μs | 1.77 | 3.1128 | 0.0916 | 50.94 KB | 2.09 |
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Method,Job,AnalyzeLaunchVariance,EvaluateOverhead,MaxAbsoluteError,MaxRelativeError,MinInvokeCount,MinIterationTime,OutlierMode,Affinity,EnvironmentVariables,Jit,LargeAddressAware,Platform,PowerPlanMode,Runtime,AllowVeryLargeObjects,Concurrent,CpuGroups,Force,HeapAffinitizeMask,HeapCount,NoAffinitize,RetainVm,Server,Arguments,BuildConfiguration,Clock,EngineFactory,NuGetReferences,Toolchain,IsMutator,InvocationCount,IterationCount,IterationTime,LaunchCount,MaxIterationCount,MaxWarmupIterationCount,MemoryRandomization,MinIterationCount,MinWarmupIterationCount,RunStrategy,UnrollFactor,WarmupCount,Mean,Error,StdDev,Ratio,Gen0,Gen1,Allocated,Alloc Ratio
BlitzCache_ConcurrentSameKey,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,12.160 μs,0.0652 μs,0.0610 μs,1.00,1.4801,0.0305,24.38 KB,1.00
MemoryCache_ConcurrentSameKey,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,7.024 μs,0.0307 μs,0.0272 μs,0.58,0.5798,0.0153,9.53 KB,0.39
LazyCache_ConcurrentSameKey,DefaultJob,False,Default,Default,Default,Default,Default,Default,1111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,Default,16,Default,21.557 μs,0.1140 μs,0.0890 μs,1.77,3.1128,0.0916,50.94 KB,2.09
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='utf-8' />
<title>BlitzCache.Benchmarks.ConcurrentSameKeyBenchmark-20250920-045406</title>

<style type="text/css">
table { border-collapse: collapse; display: block; width: 100%; overflow: auto; }
td, th { padding: 6px 13px; border: 1px solid #ddd; text-align: right; }
tr { background-color: #fff; border-top: 1px solid #ccc; }
tr:nth-child(even) { background: #f8f8f8; }
</style>
</head>
<body>
<pre><code>
BenchmarkDotNet v0.14.0, Ubuntu 24.04.3 LTS (Noble Numbat)
AMD EPYC 7763, 1 CPU, 4 logical and 2 physical cores
.NET SDK 8.0.119
[Host] : .NET 8.0.19 (8.0.1925.36514), X64 RyuJIT AVX2
DefaultJob : .NET 8.0.19 (8.0.1925.36514), X64 RyuJIT AVX2
</code></pre>
<pre><code></code></pre>

<table>
<thead><tr><th>Method </th><th>Mean</th><th>Error</th><th>StdDev</th><th>Ratio</th><th>Gen0</th><th>Gen1</th><th>Allocated</th><th>Alloc Ratio</th>
</tr>
</thead><tbody><tr><td>BlitzCache_ConcurrentSameKey</td><td>12.160 &mu;s</td><td>0.0652 &mu;s</td><td>0.0610 &mu;s</td><td>1.00</td><td>1.4801</td><td>0.0305</td><td>24.38 KB</td><td>1.00</td>
</tr><tr><td>MemoryCache_ConcurrentSameKey</td><td>7.024 &mu;s</td><td>0.0307 &mu;s</td><td>0.0272 &mu;s</td><td>0.58</td><td>0.5798</td><td>0.0153</td><td>9.53 KB</td><td>0.39</td>
</tr><tr><td>LazyCache_ConcurrentSameKey</td><td>21.557 &mu;s</td><td>0.1140 &mu;s</td><td>0.0890 &mu;s</td><td>1.77</td><td>3.1128</td><td>0.0916</td><td>50.94 KB</td><td>2.09</td>
</tr></tbody></table>
</body>
</html>
21 changes: 21 additions & 0 deletions BlitzCache.Benchmarks/BlitzCache.Benchmarks.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
<PackageReference Include="LazyCache" Version="2.4.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\BlitzCache\BlitzCacheCore.csproj" />
</ItemGroup>

</Project>
115 changes: 115 additions & 0 deletions BlitzCache.Benchmarks/CacheExpirationBenchmark.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
using BenchmarkDotNet.Attributes;
using BlitzCacheCore;
using LazyCache;
using Microsoft.Extensions.Caching.Memory;

namespace BlitzCache.Benchmarks;

/// <summary>
/// Benchmarks cache behavior with expiration scenarios to measure performance
/// when cache entries expire and need to be refreshed.
/// </summary>
[MemoryDiagnoser]
[SimpleJob]
public class CacheExpirationBenchmark
{
private const int Operations = 100;
private const int OperationDelayMs = 10;
private const int CacheExpirationMs = 200; // Short expiration to force refreshes

private IBlitzCacheInstance _blitzCache = null!;
private IMemoryCache _memoryCache = null!;
private IAppCache _lazyCache = null!;
private static int _executionCount;

[GlobalSetup]
public void Setup()
{
_blitzCache = new BlitzCacheInstance();
_memoryCache = new MemoryCache(new MemoryCacheOptions());
_lazyCache = new CachingService();
_executionCount = 0;
}

[GlobalCleanup]
public void Cleanup()
{
_blitzCache?.Dispose();
_memoryCache?.Dispose();
// LazyCache's IAppCache doesn't implement IDisposable directly
(_lazyCache as IDisposable)?.Dispose();
}

[Benchmark(Baseline = true)]
public async Task BlitzCache_WithExpiration()
{
_executionCount = 0;
const string cacheKey = "expiring_key";

for (int i = 0; i < Operations; i++)
{
await _blitzCache.BlitzGet(cacheKey, ExpensiveOperationAsync, CacheExpirationMs);

// Add small delay to allow some cache entries to expire
if (i % 10 == 0)
{
await Task.Delay(CacheExpirationMs + 50);
}
}
}

[Benchmark]
public async Task MemoryCache_WithExpiration()
{
_executionCount = 0;
const string cacheKey = "expiring_key";

for (int i = 0; i < Operations; i++)
{
await GetFromMemoryCacheAsync(cacheKey);

// Add small delay to allow some cache entries to expire
if (i % 10 == 0)
{
await Task.Delay(CacheExpirationMs + 50);
}
}
}

[Benchmark]
public async Task LazyCache_WithExpiration()
{
_executionCount = 0;
const string cacheKey = "expiring_key";

for (int i = 0; i < Operations; i++)
{
await _lazyCache.GetOrAddAsync(cacheKey, ExpensiveOperationAsync, TimeSpan.FromMilliseconds(CacheExpirationMs));

// Add small delay to allow some cache entries to expire
if (i % 10 == 0)
{
await Task.Delay(CacheExpirationMs + 50);
}
}
}

private async Task<string> ExpensiveOperationAsync()
{
Interlocked.Increment(ref _executionCount);
await Task.Delay(OperationDelayMs);
return $"Result_{DateTime.Now.Ticks}";
}

private async Task<string> GetFromMemoryCacheAsync(string key)
{
if (_memoryCache.TryGetValue(key, out string? cachedResult) && cachedResult != null)
{
return cachedResult;
}

var result = await ExpensiveOperationAsync();
_memoryCache.Set(key, result, TimeSpan.FromMilliseconds(CacheExpirationMs));
return result;
}
}
106 changes: 106 additions & 0 deletions BlitzCache.Benchmarks/ConcurrentDifferentKeysBenchmark.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
using BenchmarkDotNet.Attributes;
using BlitzCacheCore;
using LazyCache;
using Microsoft.Extensions.Caching.Memory;

namespace BlitzCache.Benchmarks;

/// <summary>
/// Benchmarks concurrent access to different cache keys to measure cache performance
/// when there's no deduplication benefit.
/// </summary>
[MemoryDiagnoser]
[SimpleJob]
public class ConcurrentDifferentKeysBenchmark
{
private const int ConcurrentTasks = 50;
private const int OperationDelayMs = 50; // Simulate expensive operation

private IBlitzCacheInstance _blitzCache = null!;
private IMemoryCache _memoryCache = null!;
private IAppCache _lazyCache = null!;
private static int _executionCount;

[GlobalSetup]
public void Setup()
{
_blitzCache = new BlitzCacheInstance();
_memoryCache = new MemoryCache(new MemoryCacheOptions());
_lazyCache = new CachingService();
_executionCount = 0;
}

[GlobalCleanup]
public void Cleanup()
{
_blitzCache?.Dispose();
_memoryCache?.Dispose();
// LazyCache's IAppCache doesn't implement IDisposable directly
(_lazyCache as IDisposable)?.Dispose();
}

[Benchmark(Baseline = true)]
public async Task BlitzCache_ConcurrentDifferentKeys()
{
_executionCount = 0;
var tasks = new Task<string>[ConcurrentTasks];

for (int i = 0; i < ConcurrentTasks; i++)
{
int taskId = i;
tasks[i] = _blitzCache.BlitzGet($"key_{taskId}", ExpensiveOperationAsync, 60000);
}

await Task.WhenAll(tasks);
// Execution count should be equal to ConcurrentTasks since all keys are different
}

[Benchmark]
public async Task MemoryCache_ConcurrentDifferentKeys()
{
_executionCount = 0;
var tasks = new Task<string>[ConcurrentTasks];

for (int i = 0; i < ConcurrentTasks; i++)
{
int taskId = i;
tasks[i] = GetFromMemoryCacheAsync($"key_{taskId}");
}

await Task.WhenAll(tasks);
}

[Benchmark]
public async Task LazyCache_ConcurrentDifferentKeys()
{
_executionCount = 0;
var tasks = new Task<string>[ConcurrentTasks];

for (int i = 0; i < ConcurrentTasks; i++)
{
int taskId = i;
tasks[i] = _lazyCache.GetOrAddAsync($"key_{taskId}", ExpensiveOperationAsync, TimeSpan.FromMinutes(1));
}

await Task.WhenAll(tasks);
}

private async Task<string> ExpensiveOperationAsync()
{
Interlocked.Increment(ref _executionCount);
await Task.Delay(OperationDelayMs);
return $"Result_{DateTime.Now.Ticks}";
}

private async Task<string> GetFromMemoryCacheAsync(string key)
{
if (_memoryCache.TryGetValue(key, out string? cachedResult) && cachedResult != null)
{
return cachedResult;
}

var result = await ExpensiveOperationAsync();
_memoryCache.Set(key, result, TimeSpan.FromMinutes(1));
return result;
}
}
Loading