Merged
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
110 changes: 42 additions & 68 deletions Benchmarks.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,15 +10,13 @@ All CacheManager instances used in the benchmarks have only one cache handle con
We are using the same configuration for all benchmarks and running two jobs each, one for x86 and one for x64. Regarding the different platforms, the conclusion is obviously that x64 is always faster than the x86 platform, but x64 consumes slightly more memory of course.

```ini
BenchmarkDotNet=v0.9.1.0
OS=Microsoft Windows NT 6.2.9200.0
Processor=Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz, ProcessorCount=8
Frequency=3328117 ticks, Resolution=300.4702 ns
HostCLR=MS.NET 4.0.30319.42000, Arch=32-bit RELEASE

Type=PutWithRegionSingleBenchmark Mode=Throughput Platform=X64
Jit=RyuJit LaunchCount=1 WarmupCount=2
TargetCount=100
BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.3194)
12th Gen Intel Core i7-12700KF, 1 CPU, 20 logical and 12 physical cores
.NET SDK 9.0.200
[Host] : .NET 8.0.13 (8.0.1325.6609), X64 RyuJIT AVX2
Job-BIBDFC : .NET 8.0.13 (8.0.1325.6609), X64 RyuJIT AVX2

IterationCount=10 LaunchCount=1 WarmupCount=2
```

### Add
Expand All@@ -27,73 +25,47 @@ TargetCount=100
Redis will be a lot slower in this scenario because CacheManager waits for the response to be able to return the `bool` value if the key has been added or not.
In general, it is good to see how fast the Dictionary handle is compared to the `System.Runtime` one. One thing you cannot see here is that also the memory footprint of the Dictionary handle is much lower.

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 1.7254 us | 0.0511 us | 1.00 |
Dictionary | X86 | 1.9563 us | 0.0399 us | 1.00 |
Runtime | X64 | 4.9839 us | 0.0778 us | 2.89 |
Runtime | X86 | 7.0324 us | 0.2012 us | 3.59 |
Redis | X64 | 56.6671 us | 1.7202 us | 32.84 |
Redis | X86 | 58.0775 us | 0.9517 us | 29.69 |

| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio |
|----------- |-------------:|------------:|------------:|-------:|--------:|-------:|-------:|----------:|------------:|
| Dictionary | 121.0 ns | 2.00 ns | 1.04 ns | 1.00 | 0.01 | 0.0153 | - | 200 B | 1.00 |
| Runtime | 637.2 ns | 28.46 ns | 16.94 ns | 5.27 | 0.14 | 0.2384 | 0.0010 | 3120 B | 15.60 |
| MsMemory | 198.8 ns | 4.59 ns | 3.04 ns | 1.64 | 0.03 | 0.0260 | - | 340 B | 1.70 |
| Redis | 105,395.6 ns | 3,758.09 ns | 2,485.74 ns | 871.20 | 20.86 | - | - | 1256 B | 6.28 |

*Adding one item per run with using region*

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 1.8094 us | 0.0386 us | 1.00 |
Dictionary | X86 | 2.5029 us | 0.1179 us | 1.00 |
Runtime | X64 | 6.6934 us | 0.1275 us | 3.70 |
Runtime | X86 | 9.2334 us | 0.1637 us | 3.69 |
Redis | X64 | 58.5355 us | 1.7054 us | 32.35 |
Redis | X86 | 61.0272 us | 1.4178 us | 24.38 |
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
|----------- |-------------:|------------:|------------:|-------:|--------:|-------:|----------:|------------:|
| Dictionary | 144.2 ns | 4.65 ns | 3.07 ns | 1.00 | 0.03 | 0.0231 | 304 B | 1.00 |
| Runtime | 342.6 ns | 18.01 ns | 11.91 ns | 2.38 | 0.09 | 0.0610 | 800 B | 2.63 |
| MsMemory | 164.1 ns | 5.74 ns | 3.80 ns | 1.14 | 0.03 | 0.0231 | 304 B | 1.00 |
| Redis | 139,200.0 ns | 4,863.92 ns | 2,894.44 ns | 966.01 | 27.25 | - | 1528 B | 5.03 |


### Put
*Put 1 item per run*
Redis is as fast as the other handles in this scenario because CacheManager uses fire and forget for those operations. For Put it doesn't matter to know if the item has been added or updated...

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 1.6802 us | 0.0235 us | 1.00 |
Dictionary | X86 | 1.9445 us | 0.0341 us | 1.00 |
Runtime | X64 | 4.4431 us | 0.0651 us | 2.64 |
Runtime | X86 | 6.5231 us | 0.1063 us | 3.35 |
Redis | X64 | 2.6869 us | 0.0934 us | 1.60 |
Redis | X86 | 3.5490 us | 0.0848 us | 1.83 |

*Put 1 item per run with region*

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 1.7401 us | 0.0365 us | 1.00 |
Dictionary | X86 | 2.4589 us | 0.1022 us | 1.00 |
Runtime | X64 | 6.1772 us | 0.3683 us | 3.55 |
Runtime | X86 | 9.9298 us | 0.5574 us | 4.04 |
Redis | X64 | 3.0807 us | 0.0906 us | 1.77 |
Redis | X86 | 3.6385 us | 0.2123 us | 1.48 |
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio |
|----------- |------------:|-----------:|-----------:|------:|--------:|-------:|-------:|----------:|------------:|
| Dictionary | 95.01 ns | 1.888 ns | 1.249 ns | 1.00 | 0.02 | 0.0122 | - | 160 B | 1.00 |
| Runtime | 887.35 ns | 19.929 ns | 13.181 ns | 9.34 | 0.18 | 0.4263 | 0.0095 | 5576 B | 34.85 |
| MsMemory | 172.40 ns | 5.030 ns | 3.327 ns | 1.81 | 0.04 | 0.0336 | - | 440 B | 2.75 |
| Redis | 4,136.37 ns | 301.420 ns | 199.371 ns | 43.54 | 2.07 | 0.0839 | 0.0610 | 1095 B | 6.84 |


### Get
*Get 1 item per run*
With `Get` operations we can clearly see how much faster an in-memory cache is, compared to the distributed variant. That's why it makes so much sense to use CacheManager with a first and secondary cache layer.

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 169.8708 ns | 4.6186 ns | 1.00 |
Dictionary | X86 | 540.0879 ns | 8.9363 ns | 1.00 |
Runtime | X64 | 310.4025 ns | 8.2928 ns | 1.83 |
Runtime | X86 | 1,030.5911 ns | 8.0532 ns | 1.91 |
Redis | X64 | 56,917.3537 ns | 2,035.4765 ns | 335.06 |
Redis | X86 | 59,519.5459 ns | 1,527.9613 ns | 110.20 |

*Get 1 item per run with region*

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 234.9676 ns | 7.9172 ns | 1.00 |
Dictionary | X86 | 600.0429 ns | 13.7090 ns | 1.00 |
Runtime | X64 | 497.4116 ns | 15.6998 ns | 2.12 |
Runtime | X86 | 1,191.6452 ns | 19.8484 ns | 1.99 |
Redis | X64 | 57,257.9868 ns | 1,705.0148 ns | 243.68 |
Redis | X86 | 61,789.3944 ns | 1,775.6064 ns | 102.97 |
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
|----------- |-------------:|-------------:|-------------:|---------:|--------:|-------:|----------:|------------:|
| Dictionary | 34.97 ns | 0.532 ns | 0.317 ns | 1.00 | 0.01 | - | - | NA |
| Runtime | 179.45 ns | 2.104 ns | 1.392 ns | 5.13 | 0.06 | 0.0153 | 200 B | NA |
| MsMemory | 75.12 ns | 0.338 ns | 0.201 ns | 2.15 | 0.02 | - | - | NA |
| Redis | 75,534.71 ns | 5,303.351 ns | 3,507.838 ns | 2,160.12 | 97.47 | 0.1221 | 2008 B | NA |


### Serializer comparison
Expand DownExpand Up@@ -149,12 +121,14 @@ Pretty simple but large enough to analyze the performance.
**Results:**


Method | Platform | Median | StdDev | Scaled | Scaled-SD |
-----: |:-------: |-----------: |----------: |-------: |-------: |
BinarySerializer | X64 | 62.4158 ms | 1.8668 ms | 2.20 | 0.07 |
JsonSerializer | X64 | 28.5521 ms | 0.4633 ms | 1.00 | 0.00 |
JsonGzSerializer | X64 | 102.5552 ms | 4.8584 ms | 3.60 | 0.18 |
ProtoBufSerializer | X64 | 11.1276 ms | 0.1252 ms | 0.39 | 0.01 |
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio |
|------------------------- |----------:|---------:|---------:|------:|--------:|--------:|-------:|----------:|------------:|
| JsonSerializer | 83.21 us | 1.163 us | 0.769 us | 1.00 | 0.01 | 14.8926 | 2.4414 | 191.16 KB | 1.00 |
| JsonGzSerializer | 346.37 us | 4.785 us | 3.165 us | 4.16 | 0.05 | 21.9727 | 2.9297 | 280.75 KB | 1.47 |
| ProtoBufSerializer | 39.18 us | 0.701 us | 0.463 us | 0.47 | 0.01 | 8.6060 | 1.0986 | 110.7 KB | 0.58 |
| BondBinarySerializer | 19.03 us | 0.398 us | 0.263 us | 0.23 | 0.00 | 4.7302 | 0.6714 | 60.53 KB | 0.32 |
| BondFastBinarySerializer | 19.42 us | 0.431 us | 0.285 us | 0.23 | 0.00 | 4.7607 | 0.7324 | 60.84 KB | 0.32 |
| BondSimpleJsonSerializer | 63.56 us | 1.132 us | 0.748 us | 0.76 | 0.01 | 11.9629 | 1.9531 | 153.35 KB | 0.80 |


As expected the protobuf serialization outperforms everything else by a huge margin!
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
<PackageReference Include="Microsoft.Garnet" Version="1.0.57" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
Expand Down
62 changes: 41 additions & 21 deletions benchmarks/CacheManager.Benchmarks/Program.cs
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,53 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Reflection;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;
using Garnet;
using Garnet.server;
using Microsoft.Extensions.Logging;

namespace CacheManager.Benchmarks
namespace CacheManager.Benchmarks;

[ExcludeFromCodeCoverage]
public class Program
{
[ExcludeFromCodeCoverage]
public class Program
public static GarnetServer StartServer(ILoggerFactory loggerFactory = null)
{
var server = new GarnetServer(new GarnetServerOptions()
{
EnableLua = true,
LuaOptions = new LuaOptions(LuaMemoryManagementMode.Native, string.Empty, TimeSpan.FromSeconds(5)),
EndPoint = new IPEndPoint(IPAddress.Loopback, 6379)
},
loggerFactory: loggerFactory);

server.Start();

return server;
}

public static void Main(string[] args)
{
public static void Main(string[] args)
StartServer();

do
{
do
{
var config = ManualConfig.CreateMinimumViable()
.AddJob(Job.Default
.WithIterationCount(10)
.WithWarmupCount(2)
.WithLaunchCount(1))
.AddDiagnoser(BenchmarkDotNet.Diagnosers.MemoryDiagnoser.Default);

BenchmarkSwitcher
.FromAssembly(typeof(Program).GetTypeInfo().Assembly)
.Run(args, config);

Console.WriteLine("done!");
Console.WriteLine("Press escape to exit or any key to continue...");
} while (Console.ReadKey().Key != ConsoleKey.Escape);
}
var config = ManualConfig.CreateMinimumViable()
.AddJob(Job.Default
.WithIterationCount(10)
.WithWarmupCount(2)
.WithLaunchCount(1))
.AddDiagnoser(BenchmarkDotNet.Diagnosers.MemoryDiagnoser.Default);

BenchmarkSwitcher
.FromAssembly(typeof(Program).GetTypeInfo().Assembly)
.Run(args, config);

Console.WriteLine("done!");
Console.WriteLine("Press escape to exit or any key to continue...");
} while (Console.ReadKey().Key != ConsoleKey.Escape);
}
}
14 changes: 0 additions & 14 deletions samples/OutputCacheExample/App_Start/BundleConfig.cs

This file was deleted.

13 changes: 0 additions & 13 deletions samples/OutputCacheExample/App_Start/FilterConfig.cs

This file was deleted.

23 changes: 0 additions & 23 deletions samples/OutputCacheExample/App_Start/RouteConfig.cs

This file was deleted.

24 changes: 0 additions & 24 deletions samples/OutputCacheExample/Content/Site.css

This file was deleted.

70 changes: 0 additions & 70 deletions samples/OutputCacheExample/Controllers/HomeController.cs

This file was deleted.

1 change: 0 additions & 1 deletion samples/OutputCacheExample/Global.asax

This file was deleted.

Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
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
110 changes: 42 additions & 68 deletions Benchmarks.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,15 +10,13 @@ All CacheManager instances used in the benchmarks have only one cache handle con
We are using the same configuration for all benchmarks and running two jobs each, one for x86 and one for x64. Regarding the different platforms, the conclusion is obviously that x64 is always faster than the x86 platform, but x64 consumes slightly more memory of course.

```ini
BenchmarkDotNet=v0.9.1.0
OS=Microsoft Windows NT 6.2.9200.0
Processor=Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz, ProcessorCount=8
Frequency=3328117 ticks, Resolution=300.4702 ns
HostCLR=MS.NET 4.0.30319.42000, Arch=32-bit RELEASE

Type=PutWithRegionSingleBenchmark Mode=Throughput Platform=X64
Jit=RyuJit LaunchCount=1 WarmupCount=2
TargetCount=100
BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.3194)
12th Gen Intel Core i7-12700KF, 1 CPU, 20 logical and 12 physical cores
.NET SDK 9.0.200
[Host] : .NET 8.0.13 (8.0.1325.6609), X64 RyuJIT AVX2
Job-BIBDFC : .NET 8.0.13 (8.0.1325.6609), X64 RyuJIT AVX2

IterationCount=10 LaunchCount=1 WarmupCount=2
```

### Add
Expand All@@ -27,73 +25,47 @@ TargetCount=100
Redis will be a lot slower in this scenario because CacheManager waits for the response to be able to return the `bool` value if the key has been added or not.
In general, it is good to see how fast the Dictionary handle is compared to the `System.Runtime` one. One thing you cannot see here is that also the memory footprint of the Dictionary handle is much lower.

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 1.7254 us | 0.0511 us | 1.00 |
Dictionary | X86 | 1.9563 us | 0.0399 us | 1.00 |
Runtime | X64 | 4.9839 us | 0.0778 us | 2.89 |
Runtime | X86 | 7.0324 us | 0.2012 us | 3.59 |
Redis | X64 | 56.6671 us | 1.7202 us | 32.84 |
Redis | X86 | 58.0775 us | 0.9517 us | 29.69 |

| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio |
|----------- |-------------:|------------:|------------:|-------:|--------:|-------:|-------:|----------:|------------:|
| Dictionary | 121.0 ns | 2.00 ns | 1.04 ns | 1.00 | 0.01 | 0.0153 | - | 200 B | 1.00 |
| Runtime | 637.2 ns | 28.46 ns | 16.94 ns | 5.27 | 0.14 | 0.2384 | 0.0010 | 3120 B | 15.60 |
| MsMemory | 198.8 ns | 4.59 ns | 3.04 ns | 1.64 | 0.03 | 0.0260 | - | 340 B | 1.70 |
| Redis | 105,395.6 ns | 3,758.09 ns | 2,485.74 ns | 871.20 | 20.86 | - | - | 1256 B | 6.28 |

*Adding one item per run with using region*

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 1.8094 us | 0.0386 us | 1.00 |
Dictionary | X86 | 2.5029 us | 0.1179 us | 1.00 |
Runtime | X64 | 6.6934 us | 0.1275 us | 3.70 |
Runtime | X86 | 9.2334 us | 0.1637 us | 3.69 |
Redis | X64 | 58.5355 us | 1.7054 us | 32.35 |
Redis | X86 | 61.0272 us | 1.4178 us | 24.38 |
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
|----------- |-------------:|------------:|------------:|-------:|--------:|-------:|----------:|------------:|
| Dictionary | 144.2 ns | 4.65 ns | 3.07 ns | 1.00 | 0.03 | 0.0231 | 304 B | 1.00 |
| Runtime | 342.6 ns | 18.01 ns | 11.91 ns | 2.38 | 0.09 | 0.0610 | 800 B | 2.63 |
| MsMemory | 164.1 ns | 5.74 ns | 3.80 ns | 1.14 | 0.03 | 0.0231 | 304 B | 1.00 |
| Redis | 139,200.0 ns | 4,863.92 ns | 2,894.44 ns | 966.01 | 27.25 | - | 1528 B | 5.03 |


### Put
*Put 1 item per run*
Redis is as fast as the other handles in this scenario because CacheManager uses fire and forget for those operations. For Put it doesn't matter to know if the item has been added or updated...

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 1.6802 us | 0.0235 us | 1.00 |
Dictionary | X86 | 1.9445 us | 0.0341 us | 1.00 |
Runtime | X64 | 4.4431 us | 0.0651 us | 2.64 |
Runtime | X86 | 6.5231 us | 0.1063 us | 3.35 |
Redis | X64 | 2.6869 us | 0.0934 us | 1.60 |
Redis | X86 | 3.5490 us | 0.0848 us | 1.83 |

*Put 1 item per run with region*

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 1.7401 us | 0.0365 us | 1.00 |
Dictionary | X86 | 2.4589 us | 0.1022 us | 1.00 |
Runtime | X64 | 6.1772 us | 0.3683 us | 3.55 |
Runtime | X86 | 9.9298 us | 0.5574 us | 4.04 |
Redis | X64 | 3.0807 us | 0.0906 us | 1.77 |
Redis | X86 | 3.6385 us | 0.2123 us | 1.48 |
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio |
|----------- |------------:|-----------:|-----------:|------:|--------:|-------:|-------:|----------:|------------:|
| Dictionary | 95.01 ns | 1.888 ns | 1.249 ns | 1.00 | 0.02 | 0.0122 | - | 160 B | 1.00 |
| Runtime | 887.35 ns | 19.929 ns | 13.181 ns | 9.34 | 0.18 | 0.4263 | 0.0095 | 5576 B | 34.85 |
| MsMemory | 172.40 ns | 5.030 ns | 3.327 ns | 1.81 | 0.04 | 0.0336 | - | 440 B | 2.75 |
| Redis | 4,136.37 ns | 301.420 ns | 199.371 ns | 43.54 | 2.07 | 0.0839 | 0.0610 | 1095 B | 6.84 |


### Get
*Get 1 item per run*
With `Get` operations we can clearly see how much faster an in-memory cache is, compared to the distributed variant. That's why it makes so much sense to use CacheManager with a first and secondary cache layer.

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 169.8708 ns | 4.6186 ns | 1.00 |
Dictionary | X86 | 540.0879 ns | 8.9363 ns | 1.00 |
Runtime | X64 | 310.4025 ns | 8.2928 ns | 1.83 |
Runtime | X86 | 1,030.5911 ns | 8.0532 ns | 1.91 |
Redis | X64 | 56,917.3537 ns | 2,035.4765 ns | 335.06 |
Redis | X86 | 59,519.5459 ns | 1,527.9613 ns | 110.20 |

*Get 1 item per run with region*

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 234.9676 ns | 7.9172 ns | 1.00 |
Dictionary | X86 | 600.0429 ns | 13.7090 ns | 1.00 |
Runtime | X64 | 497.4116 ns | 15.6998 ns | 2.12 |
Runtime | X86 | 1,191.6452 ns | 19.8484 ns | 1.99 |
Redis | X64 | 57,257.9868 ns | 1,705.0148 ns | 243.68 |
Redis | X86 | 61,789.3944 ns | 1,775.6064 ns | 102.97 |
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
|----------- |-------------:|-------------:|-------------:|---------:|--------:|-------:|----------:|------------:|
| Dictionary | 34.97 ns | 0.532 ns | 0.317 ns | 1.00 | 0.01 | - | - | NA |
| Runtime | 179.45 ns | 2.104 ns | 1.392 ns | 5.13 | 0.06 | 0.0153 | 200 B | NA |
| MsMemory | 75.12 ns | 0.338 ns | 0.201 ns | 2.15 | 0.02 | - | - | NA |
| Redis | 75,534.71 ns | 5,303.351 ns | 3,507.838 ns | 2,160.12 | 97.47 | 0.1221 | 2008 B | NA |


### Serializer comparison
Expand DownExpand Up@@ -149,12 +121,14 @@ Pretty simple but large enough to analyze the performance.
**Results:**


Method | Platform | Median | StdDev | Scaled | Scaled-SD |
-----: |:-------: |-----------: |----------: |-------: |-------: |
BinarySerializer | X64 | 62.4158 ms | 1.8668 ms | 2.20 | 0.07 |
JsonSerializer | X64 | 28.5521 ms | 0.4633 ms | 1.00 | 0.00 |
JsonGzSerializer | X64 | 102.5552 ms | 4.8584 ms | 3.60 | 0.18 |
ProtoBufSerializer | X64 | 11.1276 ms | 0.1252 ms | 0.39 | 0.01 |
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio |
|------------------------- |----------:|---------:|---------:|------:|--------:|--------:|-------:|----------:|------------:|
| JsonSerializer | 83.21 us | 1.163 us | 0.769 us | 1.00 | 0.01 | 14.8926 | 2.4414 | 191.16 KB | 1.00 |
| JsonGzSerializer | 346.37 us | 4.785 us | 3.165 us | 4.16 | 0.05 | 21.9727 | 2.9297 | 280.75 KB | 1.47 |
| ProtoBufSerializer | 39.18 us | 0.701 us | 0.463 us | 0.47 | 0.01 | 8.6060 | 1.0986 | 110.7 KB | 0.58 |
| BondBinarySerializer | 19.03 us | 0.398 us | 0.263 us | 0.23 | 0.00 | 4.7302 | 0.6714 | 60.53 KB | 0.32 |
| BondFastBinarySerializer | 19.42 us | 0.431 us | 0.285 us | 0.23 | 0.00 | 4.7607 | 0.7324 | 60.84 KB | 0.32 |
| BondSimpleJsonSerializer | 63.56 us | 1.132 us | 0.748 us | 0.76 | 0.01 | 11.9629 | 1.9531 | 153.35 KB | 0.80 |


As expected the protobuf serialization outperforms everything else by a huge margin!
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
<PackageReference Include="Microsoft.Garnet" Version="1.0.57" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
Expand Down
62 changes: 41 additions & 21 deletions benchmarks/CacheManager.Benchmarks/Program.cs
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,53 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Reflection;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;
using Garnet;
using Garnet.server;
using Microsoft.Extensions.Logging;

namespace CacheManager.Benchmarks
namespace CacheManager.Benchmarks;

[ExcludeFromCodeCoverage]
public class Program
{
[ExcludeFromCodeCoverage]
public class Program
public static GarnetServer StartServer(ILoggerFactory loggerFactory = null)
{
var server = new GarnetServer(new GarnetServerOptions()
{
EnableLua = true,
LuaOptions = new LuaOptions(LuaMemoryManagementMode.Native, string.Empty, TimeSpan.FromSeconds(5)),
EndPoint = new IPEndPoint(IPAddress.Loopback, 6379)
},
loggerFactory: loggerFactory);

server.Start();

return server;
}

public static void Main(string[] args)
{
public static void Main(string[] args)
StartServer();

do
{
do
{
var config = ManualConfig.CreateMinimumViable()
.AddJob(Job.Default
.WithIterationCount(10)
.WithWarmupCount(2)
.WithLaunchCount(1))
.AddDiagnoser(BenchmarkDotNet.Diagnosers.MemoryDiagnoser.Default);

BenchmarkSwitcher
.FromAssembly(typeof(Program).GetTypeInfo().Assembly)
.Run(args, config);

Console.WriteLine("done!");
Console.WriteLine("Press escape to exit or any key to continue...");
} while (Console.ReadKey().Key != ConsoleKey.Escape);
}
var config = ManualConfig.CreateMinimumViable()
.AddJob(Job.Default
.WithIterationCount(10)
.WithWarmupCount(2)
.WithLaunchCount(1))
.AddDiagnoser(BenchmarkDotNet.Diagnosers.MemoryDiagnoser.Default);

BenchmarkSwitcher
.FromAssembly(typeof(Program).GetTypeInfo().Assembly)
.Run(args, config);

Console.WriteLine("done!");
Console.WriteLine("Press escape to exit or any key to continue...");
} while (Console.ReadKey().Key != ConsoleKey.Escape);
}
}
14 changes: 0 additions & 14 deletions samples/OutputCacheExample/App_Start/BundleConfig.cs

This file was deleted.

13 changes: 0 additions & 13 deletions samples/OutputCacheExample/App_Start/FilterConfig.cs

This file was deleted.

23 changes: 0 additions & 23 deletions samples/OutputCacheExample/App_Start/RouteConfig.cs

This file was deleted.

24 changes: 0 additions & 24 deletions samples/OutputCacheExample/Content/Site.css

This file was deleted.

70 changes: 0 additions & 70 deletions samples/OutputCacheExample/Controllers/HomeController.cs

This file was deleted.

1 change: 0 additions & 1 deletion samples/OutputCacheExample/Global.asax

This file was deleted.

Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
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
110 changes: 42 additions & 68 deletions Benchmarks.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,15 +10,13 @@ All CacheManager instances used in the benchmarks have only one cache handle con
We are using the same configuration for all benchmarks and running two jobs each, one for x86 and one for x64. Regarding the different platforms, the conclusion is obviously that x64 is always faster than the x86 platform, but x64 consumes slightly more memory of course.

```ini
BenchmarkDotNet=v0.9.1.0
OS=Microsoft Windows NT 6.2.9200.0
Processor=Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz, ProcessorCount=8
Frequency=3328117 ticks, Resolution=300.4702 ns
HostCLR=MS.NET 4.0.30319.42000, Arch=32-bit RELEASE

Type=PutWithRegionSingleBenchmark Mode=Throughput Platform=X64
Jit=RyuJit LaunchCount=1 WarmupCount=2
TargetCount=100
BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.3194)
12th Gen Intel Core i7-12700KF, 1 CPU, 20 logical and 12 physical cores
.NET SDK 9.0.200
[Host] : .NET 8.0.13 (8.0.1325.6609), X64 RyuJIT AVX2
Job-BIBDFC : .NET 8.0.13 (8.0.1325.6609), X64 RyuJIT AVX2

IterationCount=10 LaunchCount=1 WarmupCount=2
```

### Add
Expand All@@ -27,73 +25,47 @@ TargetCount=100
Redis will be a lot slower in this scenario because CacheManager waits for the response to be able to return the `bool` value if the key has been added or not.
In general, it is good to see how fast the Dictionary handle is compared to the `System.Runtime` one. One thing you cannot see here is that also the memory footprint of the Dictionary handle is much lower.

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 1.7254 us | 0.0511 us | 1.00 |
Dictionary | X86 | 1.9563 us | 0.0399 us | 1.00 |
Runtime | X64 | 4.9839 us | 0.0778 us | 2.89 |
Runtime | X86 | 7.0324 us | 0.2012 us | 3.59 |
Redis | X64 | 56.6671 us | 1.7202 us | 32.84 |
Redis | X86 | 58.0775 us | 0.9517 us | 29.69 |

| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio |
|----------- |-------------:|------------:|------------:|-------:|--------:|-------:|-------:|----------:|------------:|
| Dictionary | 121.0 ns | 2.00 ns | 1.04 ns | 1.00 | 0.01 | 0.0153 | - | 200 B | 1.00 |
| Runtime | 637.2 ns | 28.46 ns | 16.94 ns | 5.27 | 0.14 | 0.2384 | 0.0010 | 3120 B | 15.60 |
| MsMemory | 198.8 ns | 4.59 ns | 3.04 ns | 1.64 | 0.03 | 0.0260 | - | 340 B | 1.70 |
| Redis | 105,395.6 ns | 3,758.09 ns | 2,485.74 ns | 871.20 | 20.86 | - | - | 1256 B | 6.28 |

*Adding one item per run with using region*

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 1.8094 us | 0.0386 us | 1.00 |
Dictionary | X86 | 2.5029 us | 0.1179 us | 1.00 |
Runtime | X64 | 6.6934 us | 0.1275 us | 3.70 |
Runtime | X86 | 9.2334 us | 0.1637 us | 3.69 |
Redis | X64 | 58.5355 us | 1.7054 us | 32.35 |
Redis | X86 | 61.0272 us | 1.4178 us | 24.38 |
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
|----------- |-------------:|------------:|------------:|-------:|--------:|-------:|----------:|------------:|
| Dictionary | 144.2 ns | 4.65 ns | 3.07 ns | 1.00 | 0.03 | 0.0231 | 304 B | 1.00 |
| Runtime | 342.6 ns | 18.01 ns | 11.91 ns | 2.38 | 0.09 | 0.0610 | 800 B | 2.63 |
| MsMemory | 164.1 ns | 5.74 ns | 3.80 ns | 1.14 | 0.03 | 0.0231 | 304 B | 1.00 |
| Redis | 139,200.0 ns | 4,863.92 ns | 2,894.44 ns | 966.01 | 27.25 | - | 1528 B | 5.03 |


### Put
*Put 1 item per run*
Redis is as fast as the other handles in this scenario because CacheManager uses fire and forget for those operations. For Put it doesn't matter to know if the item has been added or updated...

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 1.6802 us | 0.0235 us | 1.00 |
Dictionary | X86 | 1.9445 us | 0.0341 us | 1.00 |
Runtime | X64 | 4.4431 us | 0.0651 us | 2.64 |
Runtime | X86 | 6.5231 us | 0.1063 us | 3.35 |
Redis | X64 | 2.6869 us | 0.0934 us | 1.60 |
Redis | X86 | 3.5490 us | 0.0848 us | 1.83 |

*Put 1 item per run with region*

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 1.7401 us | 0.0365 us | 1.00 |
Dictionary | X86 | 2.4589 us | 0.1022 us | 1.00 |
Runtime | X64 | 6.1772 us | 0.3683 us | 3.55 |
Runtime | X86 | 9.9298 us | 0.5574 us | 4.04 |
Redis | X64 | 3.0807 us | 0.0906 us | 1.77 |
Redis | X86 | 3.6385 us | 0.2123 us | 1.48 |
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio |
|----------- |------------:|-----------:|-----------:|------:|--------:|-------:|-------:|----------:|------------:|
| Dictionary | 95.01 ns | 1.888 ns | 1.249 ns | 1.00 | 0.02 | 0.0122 | - | 160 B | 1.00 |
| Runtime | 887.35 ns | 19.929 ns | 13.181 ns | 9.34 | 0.18 | 0.4263 | 0.0095 | 5576 B | 34.85 |
| MsMemory | 172.40 ns | 5.030 ns | 3.327 ns | 1.81 | 0.04 | 0.0336 | - | 440 B | 2.75 |
| Redis | 4,136.37 ns | 301.420 ns | 199.371 ns | 43.54 | 2.07 | 0.0839 | 0.0610 | 1095 B | 6.84 |


### Get
*Get 1 item per run*
With `Get` operations we can clearly see how much faster an in-memory cache is, compared to the distributed variant. That's why it makes so much sense to use CacheManager with a first and secondary cache layer.

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 169.8708 ns | 4.6186 ns | 1.00 |
Dictionary | X86 | 540.0879 ns | 8.9363 ns | 1.00 |
Runtime | X64 | 310.4025 ns | 8.2928 ns | 1.83 |
Runtime | X86 | 1,030.5911 ns | 8.0532 ns | 1.91 |
Redis | X64 | 56,917.3537 ns | 2,035.4765 ns | 335.06 |
Redis | X86 | 59,519.5459 ns | 1,527.9613 ns | 110.20 |

*Get 1 item per run with region*

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 234.9676 ns | 7.9172 ns | 1.00 |
Dictionary | X86 | 600.0429 ns | 13.7090 ns | 1.00 |
Runtime | X64 | 497.4116 ns | 15.6998 ns | 2.12 |
Runtime | X86 | 1,191.6452 ns | 19.8484 ns | 1.99 |
Redis | X64 | 57,257.9868 ns | 1,705.0148 ns | 243.68 |
Redis | X86 | 61,789.3944 ns | 1,775.6064 ns | 102.97 |
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
|----------- |-------------:|-------------:|-------------:|---------:|--------:|-------:|----------:|------------:|
| Dictionary | 34.97 ns | 0.532 ns | 0.317 ns | 1.00 | 0.01 | - | - | NA |
| Runtime | 179.45 ns | 2.104 ns | 1.392 ns | 5.13 | 0.06 | 0.0153 | 200 B | NA |
| MsMemory | 75.12 ns | 0.338 ns | 0.201 ns | 2.15 | 0.02 | - | - | NA |
| Redis | 75,534.71 ns | 5,303.351 ns | 3,507.838 ns | 2,160.12 | 97.47 | 0.1221 | 2008 B | NA |


### Serializer comparison
Expand DownExpand Up@@ -149,12 +121,14 @@ Pretty simple but large enough to analyze the performance.
**Results:**


Method | Platform | Median | StdDev | Scaled | Scaled-SD |
-----: |:-------: |-----------: |----------: |-------: |-------: |
BinarySerializer | X64 | 62.4158 ms | 1.8668 ms | 2.20 | 0.07 |
JsonSerializer | X64 | 28.5521 ms | 0.4633 ms | 1.00 | 0.00 |
JsonGzSerializer | X64 | 102.5552 ms | 4.8584 ms | 3.60 | 0.18 |
ProtoBufSerializer | X64 | 11.1276 ms | 0.1252 ms | 0.39 | 0.01 |
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio |
|------------------------- |----------:|---------:|---------:|------:|--------:|--------:|-------:|----------:|------------:|
| JsonSerializer | 83.21 us | 1.163 us | 0.769 us | 1.00 | 0.01 | 14.8926 | 2.4414 | 191.16 KB | 1.00 |
| JsonGzSerializer | 346.37 us | 4.785 us | 3.165 us | 4.16 | 0.05 | 21.9727 | 2.9297 | 280.75 KB | 1.47 |
| ProtoBufSerializer | 39.18 us | 0.701 us | 0.463 us | 0.47 | 0.01 | 8.6060 | 1.0986 | 110.7 KB | 0.58 |
| BondBinarySerializer | 19.03 us | 0.398 us | 0.263 us | 0.23 | 0.00 | 4.7302 | 0.6714 | 60.53 KB | 0.32 |
| BondFastBinarySerializer | 19.42 us | 0.431 us | 0.285 us | 0.23 | 0.00 | 4.7607 | 0.7324 | 60.84 KB | 0.32 |
| BondSimpleJsonSerializer | 63.56 us | 1.132 us | 0.748 us | 0.76 | 0.01 | 11.9629 | 1.9531 | 153.35 KB | 0.80 |


As expected the protobuf serialization outperforms everything else by a huge margin!
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
<PackageReference Include="Microsoft.Garnet" Version="1.0.57" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
Expand Down
62 changes: 41 additions & 21 deletions benchmarks/CacheManager.Benchmarks/Program.cs
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,53 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Reflection;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;
using Garnet;
using Garnet.server;
using Microsoft.Extensions.Logging;

namespace CacheManager.Benchmarks
namespace CacheManager.Benchmarks;

[ExcludeFromCodeCoverage]
public class Program
{
[ExcludeFromCodeCoverage]
public class Program
public static GarnetServer StartServer(ILoggerFactory loggerFactory = null)
{
var server = new GarnetServer(new GarnetServerOptions()
{
EnableLua = true,
LuaOptions = new LuaOptions(LuaMemoryManagementMode.Native, string.Empty, TimeSpan.FromSeconds(5)),
EndPoint = new IPEndPoint(IPAddress.Loopback, 6379)
},
loggerFactory: loggerFactory);

server.Start();

return server;
}

public static void Main(string[] args)
{
public static void Main(string[] args)
StartServer();

do
{
do
{
var config = ManualConfig.CreateMinimumViable()
.AddJob(Job.Default
.WithIterationCount(10)
.WithWarmupCount(2)
.WithLaunchCount(1))
.AddDiagnoser(BenchmarkDotNet.Diagnosers.MemoryDiagnoser.Default);

BenchmarkSwitcher
.FromAssembly(typeof(Program).GetTypeInfo().Assembly)
.Run(args, config);

Console.WriteLine("done!");
Console.WriteLine("Press escape to exit or any key to continue...");
} while (Console.ReadKey().Key != ConsoleKey.Escape);
}
var config = ManualConfig.CreateMinimumViable()
.AddJob(Job.Default
.WithIterationCount(10)
.WithWarmupCount(2)
.WithLaunchCount(1))
.AddDiagnoser(BenchmarkDotNet.Diagnosers.MemoryDiagnoser.Default);

BenchmarkSwitcher
.FromAssembly(typeof(Program).GetTypeInfo().Assembly)
.Run(args, config);

Console.WriteLine("done!");
Console.WriteLine("Press escape to exit or any key to continue...");
} while (Console.ReadKey().Key != ConsoleKey.Escape);
}
}
14 changes: 0 additions & 14 deletions samples/OutputCacheExample/App_Start/BundleConfig.cs

This file was deleted.

13 changes: 0 additions & 13 deletions samples/OutputCacheExample/App_Start/FilterConfig.cs

This file was deleted.

23 changes: 0 additions & 23 deletions samples/OutputCacheExample/App_Start/RouteConfig.cs

This file was deleted.

24 changes: 0 additions & 24 deletions samples/OutputCacheExample/Content/Site.css

This file was deleted.

70 changes: 0 additions & 70 deletions samples/OutputCacheExample/Controllers/HomeController.cs

This file was deleted.

1 change: 0 additions & 1 deletion samples/OutputCacheExample/Global.asax

This file was deleted.

Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
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
110 changes: 42 additions & 68 deletions Benchmarks.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,15 +10,13 @@ All CacheManager instances used in the benchmarks have only one cache handle con
We are using the same configuration for all benchmarks and running two jobs each, one for x86 and one for x64. Regarding the different platforms, the conclusion is obviously that x64 is always faster than the x86 platform, but x64 consumes slightly more memory of course.

```ini
BenchmarkDotNet=v0.9.1.0
OS=Microsoft Windows NT 6.2.9200.0
Processor=Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz, ProcessorCount=8
Frequency=3328117 ticks, Resolution=300.4702 ns
HostCLR=MS.NET 4.0.30319.42000, Arch=32-bit RELEASE

Type=PutWithRegionSingleBenchmark Mode=Throughput Platform=X64
Jit=RyuJit LaunchCount=1 WarmupCount=2
TargetCount=100
BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.3194)
12th Gen Intel Core i7-12700KF, 1 CPU, 20 logical and 12 physical cores
.NET SDK 9.0.200
[Host] : .NET 8.0.13 (8.0.1325.6609), X64 RyuJIT AVX2
Job-BIBDFC : .NET 8.0.13 (8.0.1325.6609), X64 RyuJIT AVX2

IterationCount=10 LaunchCount=1 WarmupCount=2
```

### Add
Expand All@@ -27,73 +25,47 @@ TargetCount=100
Redis will be a lot slower in this scenario because CacheManager waits for the response to be able to return the `bool` value if the key has been added or not.
In general, it is good to see how fast the Dictionary handle is compared to the `System.Runtime` one. One thing you cannot see here is that also the memory footprint of the Dictionary handle is much lower.

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 1.7254 us | 0.0511 us | 1.00 |
Dictionary | X86 | 1.9563 us | 0.0399 us | 1.00 |
Runtime | X64 | 4.9839 us | 0.0778 us | 2.89 |
Runtime | X86 | 7.0324 us | 0.2012 us | 3.59 |
Redis | X64 | 56.6671 us | 1.7202 us | 32.84 |
Redis | X86 | 58.0775 us | 0.9517 us | 29.69 |

| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio |
|----------- |-------------:|------------:|------------:|-------:|--------:|-------:|-------:|----------:|------------:|
| Dictionary | 121.0 ns | 2.00 ns | 1.04 ns | 1.00 | 0.01 | 0.0153 | - | 200 B | 1.00 |
| Runtime | 637.2 ns | 28.46 ns | 16.94 ns | 5.27 | 0.14 | 0.2384 | 0.0010 | 3120 B | 15.60 |
| MsMemory | 198.8 ns | 4.59 ns | 3.04 ns | 1.64 | 0.03 | 0.0260 | - | 340 B | 1.70 |
| Redis | 105,395.6 ns | 3,758.09 ns | 2,485.74 ns | 871.20 | 20.86 | - | - | 1256 B | 6.28 |

*Adding one item per run with using region*

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 1.8094 us | 0.0386 us | 1.00 |
Dictionary | X86 | 2.5029 us | 0.1179 us | 1.00 |
Runtime | X64 | 6.6934 us | 0.1275 us | 3.70 |
Runtime | X86 | 9.2334 us | 0.1637 us | 3.69 |
Redis | X64 | 58.5355 us | 1.7054 us | 32.35 |
Redis | X86 | 61.0272 us | 1.4178 us | 24.38 |
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
|----------- |-------------:|------------:|------------:|-------:|--------:|-------:|----------:|------------:|
| Dictionary | 144.2 ns | 4.65 ns | 3.07 ns | 1.00 | 0.03 | 0.0231 | 304 B | 1.00 |
| Runtime | 342.6 ns | 18.01 ns | 11.91 ns | 2.38 | 0.09 | 0.0610 | 800 B | 2.63 |
| MsMemory | 164.1 ns | 5.74 ns | 3.80 ns | 1.14 | 0.03 | 0.0231 | 304 B | 1.00 |
| Redis | 139,200.0 ns | 4,863.92 ns | 2,894.44 ns | 966.01 | 27.25 | - | 1528 B | 5.03 |


### Put
*Put 1 item per run*
Redis is as fast as the other handles in this scenario because CacheManager uses fire and forget for those operations. For Put it doesn't matter to know if the item has been added or updated...

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 1.6802 us | 0.0235 us | 1.00 |
Dictionary | X86 | 1.9445 us | 0.0341 us | 1.00 |
Runtime | X64 | 4.4431 us | 0.0651 us | 2.64 |
Runtime | X86 | 6.5231 us | 0.1063 us | 3.35 |
Redis | X64 | 2.6869 us | 0.0934 us | 1.60 |
Redis | X86 | 3.5490 us | 0.0848 us | 1.83 |

*Put 1 item per run with region*

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 1.7401 us | 0.0365 us | 1.00 |
Dictionary | X86 | 2.4589 us | 0.1022 us | 1.00 |
Runtime | X64 | 6.1772 us | 0.3683 us | 3.55 |
Runtime | X86 | 9.9298 us | 0.5574 us | 4.04 |
Redis | X64 | 3.0807 us | 0.0906 us | 1.77 |
Redis | X86 | 3.6385 us | 0.2123 us | 1.48 |
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio |
|----------- |------------:|-----------:|-----------:|------:|--------:|-------:|-------:|----------:|------------:|
| Dictionary | 95.01 ns | 1.888 ns | 1.249 ns | 1.00 | 0.02 | 0.0122 | - | 160 B | 1.00 |
| Runtime | 887.35 ns | 19.929 ns | 13.181 ns | 9.34 | 0.18 | 0.4263 | 0.0095 | 5576 B | 34.85 |
| MsMemory | 172.40 ns | 5.030 ns | 3.327 ns | 1.81 | 0.04 | 0.0336 | - | 440 B | 2.75 |
| Redis | 4,136.37 ns | 301.420 ns | 199.371 ns | 43.54 | 2.07 | 0.0839 | 0.0610 | 1095 B | 6.84 |


### Get
*Get 1 item per run*
With `Get` operations we can clearly see how much faster an in-memory cache is, compared to the distributed variant. That's why it makes so much sense to use CacheManager with a first and secondary cache layer.

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 169.8708 ns | 4.6186 ns | 1.00 |
Dictionary | X86 | 540.0879 ns | 8.9363 ns | 1.00 |
Runtime | X64 | 310.4025 ns | 8.2928 ns | 1.83 |
Runtime | X86 | 1,030.5911 ns | 8.0532 ns | 1.91 |
Redis | X64 | 56,917.3537 ns | 2,035.4765 ns | 335.06 |
Redis | X86 | 59,519.5459 ns | 1,527.9613 ns | 110.20 |

*Get 1 item per run with region*

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 234.9676 ns | 7.9172 ns | 1.00 |
Dictionary | X86 | 600.0429 ns | 13.7090 ns | 1.00 |
Runtime | X64 | 497.4116 ns | 15.6998 ns | 2.12 |
Runtime | X86 | 1,191.6452 ns | 19.8484 ns | 1.99 |
Redis | X64 | 57,257.9868 ns | 1,705.0148 ns | 243.68 |
Redis | X86 | 61,789.3944 ns | 1,775.6064 ns | 102.97 |
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
|----------- |-------------:|-------------:|-------------:|---------:|--------:|-------:|----------:|------------:|
| Dictionary | 34.97 ns | 0.532 ns | 0.317 ns | 1.00 | 0.01 | - | - | NA |
| Runtime | 179.45 ns | 2.104 ns | 1.392 ns | 5.13 | 0.06 | 0.0153 | 200 B | NA |
| MsMemory | 75.12 ns | 0.338 ns | 0.201 ns | 2.15 | 0.02 | - | - | NA |
| Redis | 75,534.71 ns | 5,303.351 ns | 3,507.838 ns | 2,160.12 | 97.47 | 0.1221 | 2008 B | NA |


### Serializer comparison
Expand DownExpand Up@@ -149,12 +121,14 @@ Pretty simple but large enough to analyze the performance.
**Results:**


Method | Platform | Median | StdDev | Scaled | Scaled-SD |
-----: |:-------: |-----------: |----------: |-------: |-------: |
BinarySerializer | X64 | 62.4158 ms | 1.8668 ms | 2.20 | 0.07 |
JsonSerializer | X64 | 28.5521 ms | 0.4633 ms | 1.00 | 0.00 |
JsonGzSerializer | X64 | 102.5552 ms | 4.8584 ms | 3.60 | 0.18 |
ProtoBufSerializer | X64 | 11.1276 ms | 0.1252 ms | 0.39 | 0.01 |
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio |
|------------------------- |----------:|---------:|---------:|------:|--------:|--------:|-------:|----------:|------------:|
| JsonSerializer | 83.21 us | 1.163 us | 0.769 us | 1.00 | 0.01 | 14.8926 | 2.4414 | 191.16 KB | 1.00 |
| JsonGzSerializer | 346.37 us | 4.785 us | 3.165 us | 4.16 | 0.05 | 21.9727 | 2.9297 | 280.75 KB | 1.47 |
| ProtoBufSerializer | 39.18 us | 0.701 us | 0.463 us | 0.47 | 0.01 | 8.6060 | 1.0986 | 110.7 KB | 0.58 |
| BondBinarySerializer | 19.03 us | 0.398 us | 0.263 us | 0.23 | 0.00 | 4.7302 | 0.6714 | 60.53 KB | 0.32 |
| BondFastBinarySerializer | 19.42 us | 0.431 us | 0.285 us | 0.23 | 0.00 | 4.7607 | 0.7324 | 60.84 KB | 0.32 |
| BondSimpleJsonSerializer | 63.56 us | 1.132 us | 0.748 us | 0.76 | 0.01 | 11.9629 | 1.9531 | 153.35 KB | 0.80 |


As expected the protobuf serialization outperforms everything else by a huge margin!
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
<PackageReference Include="Microsoft.Garnet" Version="1.0.57" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
Expand Down
62 changes: 41 additions & 21 deletions benchmarks/CacheManager.Benchmarks/Program.cs
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,53 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Reflection;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;
using Garnet;
using Garnet.server;
using Microsoft.Extensions.Logging;

namespace CacheManager.Benchmarks
namespace CacheManager.Benchmarks;

[ExcludeFromCodeCoverage]
public class Program
{
[ExcludeFromCodeCoverage]
public class Program
public static GarnetServer StartServer(ILoggerFactory loggerFactory = null)
{
var server = new GarnetServer(new GarnetServerOptions()
{
EnableLua = true,
LuaOptions = new LuaOptions(LuaMemoryManagementMode.Native, string.Empty, TimeSpan.FromSeconds(5)),
EndPoint = new IPEndPoint(IPAddress.Loopback, 6379)
},
loggerFactory: loggerFactory);

server.Start();

return server;
}

public static void Main(string[] args)
{
public static void Main(string[] args)
StartServer();

do
{
do
{
var config = ManualConfig.CreateMinimumViable()
.AddJob(Job.Default
.WithIterationCount(10)
.WithWarmupCount(2)
.WithLaunchCount(1))
.AddDiagnoser(BenchmarkDotNet.Diagnosers.MemoryDiagnoser.Default);

BenchmarkSwitcher
.FromAssembly(typeof(Program).GetTypeInfo().Assembly)
.Run(args, config);

Console.WriteLine("done!");
Console.WriteLine("Press escape to exit or any key to continue...");
} while (Console.ReadKey().Key != ConsoleKey.Escape);
}
var config = ManualConfig.CreateMinimumViable()
.AddJob(Job.Default
.WithIterationCount(10)
.WithWarmupCount(2)
.WithLaunchCount(1))
.AddDiagnoser(BenchmarkDotNet.Diagnosers.MemoryDiagnoser.Default);

BenchmarkSwitcher
.FromAssembly(typeof(Program).GetTypeInfo().Assembly)
.Run(args, config);

Console.WriteLine("done!");
Console.WriteLine("Press escape to exit or any key to continue...");
} while (Console.ReadKey().Key != ConsoleKey.Escape);
}
}
14 changes: 0 additions & 14 deletions samples/OutputCacheExample/App_Start/BundleConfig.cs

This file was deleted.

13 changes: 0 additions & 13 deletions samples/OutputCacheExample/App_Start/FilterConfig.cs

This file was deleted.

23 changes: 0 additions & 23 deletions samples/OutputCacheExample/App_Start/RouteConfig.cs

This file was deleted.

24 changes: 0 additions & 24 deletions samples/OutputCacheExample/Content/Site.css

This file was deleted.

70 changes: 0 additions & 70 deletions samples/OutputCacheExample/Controllers/HomeController.cs

This file was deleted.

1 change: 0 additions & 1 deletion samples/OutputCacheExample/Global.asax

This file was deleted.

Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
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
110 changes: 42 additions & 68 deletions Benchmarks.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,15 +10,13 @@ All CacheManager instances used in the benchmarks have only one cache handle con
We are using the same configuration for all benchmarks and running two jobs each, one for x86 and one for x64. Regarding the different platforms, the conclusion is obviously that x64 is always faster than the x86 platform, but x64 consumes slightly more memory of course.

```ini
BenchmarkDotNet=v0.9.1.0
OS=Microsoft Windows NT 6.2.9200.0
Processor=Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz, ProcessorCount=8
Frequency=3328117 ticks, Resolution=300.4702 ns
HostCLR=MS.NET 4.0.30319.42000, Arch=32-bit RELEASE

Type=PutWithRegionSingleBenchmark Mode=Throughput Platform=X64
Jit=RyuJit LaunchCount=1 WarmupCount=2
TargetCount=100
BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.3194)
12th Gen Intel Core i7-12700KF, 1 CPU, 20 logical and 12 physical cores
.NET SDK 9.0.200
[Host] : .NET 8.0.13 (8.0.1325.6609), X64 RyuJIT AVX2
Job-BIBDFC : .NET 8.0.13 (8.0.1325.6609), X64 RyuJIT AVX2

IterationCount=10 LaunchCount=1 WarmupCount=2
```

### Add
Expand All@@ -27,73 +25,47 @@ TargetCount=100
Redis will be a lot slower in this scenario because CacheManager waits for the response to be able to return the `bool` value if the key has been added or not.
In general, it is good to see how fast the Dictionary handle is compared to the `System.Runtime` one. One thing you cannot see here is that also the memory footprint of the Dictionary handle is much lower.

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 1.7254 us | 0.0511 us | 1.00 |
Dictionary | X86 | 1.9563 us | 0.0399 us | 1.00 |
Runtime | X64 | 4.9839 us | 0.0778 us | 2.89 |
Runtime | X86 | 7.0324 us | 0.2012 us | 3.59 |
Redis | X64 | 56.6671 us | 1.7202 us | 32.84 |
Redis | X86 | 58.0775 us | 0.9517 us | 29.69 |

| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio |
|----------- |-------------:|------------:|------------:|-------:|--------:|-------:|-------:|----------:|------------:|
| Dictionary | 121.0 ns | 2.00 ns | 1.04 ns | 1.00 | 0.01 | 0.0153 | - | 200 B | 1.00 |
| Runtime | 637.2 ns | 28.46 ns | 16.94 ns | 5.27 | 0.14 | 0.2384 | 0.0010 | 3120 B | 15.60 |
| MsMemory | 198.8 ns | 4.59 ns | 3.04 ns | 1.64 | 0.03 | 0.0260 | - | 340 B | 1.70 |
| Redis | 105,395.6 ns | 3,758.09 ns | 2,485.74 ns | 871.20 | 20.86 | - | - | 1256 B | 6.28 |

*Adding one item per run with using region*

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 1.8094 us | 0.0386 us | 1.00 |
Dictionary | X86 | 2.5029 us | 0.1179 us | 1.00 |
Runtime | X64 | 6.6934 us | 0.1275 us | 3.70 |
Runtime | X86 | 9.2334 us | 0.1637 us | 3.69 |
Redis | X64 | 58.5355 us | 1.7054 us | 32.35 |
Redis | X86 | 61.0272 us | 1.4178 us | 24.38 |
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
|----------- |-------------:|------------:|------------:|-------:|--------:|-------:|----------:|------------:|
| Dictionary | 144.2 ns | 4.65 ns | 3.07 ns | 1.00 | 0.03 | 0.0231 | 304 B | 1.00 |
| Runtime | 342.6 ns | 18.01 ns | 11.91 ns | 2.38 | 0.09 | 0.0610 | 800 B | 2.63 |
| MsMemory | 164.1 ns | 5.74 ns | 3.80 ns | 1.14 | 0.03 | 0.0231 | 304 B | 1.00 |
| Redis | 139,200.0 ns | 4,863.92 ns | 2,894.44 ns | 966.01 | 27.25 | - | 1528 B | 5.03 |


### Put
*Put 1 item per run*
Redis is as fast as the other handles in this scenario because CacheManager uses fire and forget for those operations. For Put it doesn't matter to know if the item has been added or updated...

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 1.6802 us | 0.0235 us | 1.00 |
Dictionary | X86 | 1.9445 us | 0.0341 us | 1.00 |
Runtime | X64 | 4.4431 us | 0.0651 us | 2.64 |
Runtime | X86 | 6.5231 us | 0.1063 us | 3.35 |
Redis | X64 | 2.6869 us | 0.0934 us | 1.60 |
Redis | X86 | 3.5490 us | 0.0848 us | 1.83 |

*Put 1 item per run with region*

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 1.7401 us | 0.0365 us | 1.00 |
Dictionary | X86 | 2.4589 us | 0.1022 us | 1.00 |
Runtime | X64 | 6.1772 us | 0.3683 us | 3.55 |
Runtime | X86 | 9.9298 us | 0.5574 us | 4.04 |
Redis | X64 | 3.0807 us | 0.0906 us | 1.77 |
Redis | X86 | 3.6385 us | 0.2123 us | 1.48 |
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio |
|----------- |------------:|-----------:|-----------:|------:|--------:|-------:|-------:|----------:|------------:|
| Dictionary | 95.01 ns | 1.888 ns | 1.249 ns | 1.00 | 0.02 | 0.0122 | - | 160 B | 1.00 |
| Runtime | 887.35 ns | 19.929 ns | 13.181 ns | 9.34 | 0.18 | 0.4263 | 0.0095 | 5576 B | 34.85 |
| MsMemory | 172.40 ns | 5.030 ns | 3.327 ns | 1.81 | 0.04 | 0.0336 | - | 440 B | 2.75 |
| Redis | 4,136.37 ns | 301.420 ns | 199.371 ns | 43.54 | 2.07 | 0.0839 | 0.0610 | 1095 B | 6.84 |


### Get
*Get 1 item per run*
With `Get` operations we can clearly see how much faster an in-memory cache is, compared to the distributed variant. That's why it makes so much sense to use CacheManager with a first and secondary cache layer.

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 169.8708 ns | 4.6186 ns | 1.00 |
Dictionary | X86 | 540.0879 ns | 8.9363 ns | 1.00 |
Runtime | X64 | 310.4025 ns | 8.2928 ns | 1.83 |
Runtime | X86 | 1,030.5911 ns | 8.0532 ns | 1.91 |
Redis | X64 | 56,917.3537 ns | 2,035.4765 ns | 335.06 |
Redis | X86 | 59,519.5459 ns | 1,527.9613 ns | 110.20 |

*Get 1 item per run with region*

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 234.9676 ns | 7.9172 ns | 1.00 |
Dictionary | X86 | 600.0429 ns | 13.7090 ns | 1.00 |
Runtime | X64 | 497.4116 ns | 15.6998 ns | 2.12 |
Runtime | X86 | 1,191.6452 ns | 19.8484 ns | 1.99 |
Redis | X64 | 57,257.9868 ns | 1,705.0148 ns | 243.68 |
Redis | X86 | 61,789.3944 ns | 1,775.6064 ns | 102.97 |
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
|----------- |-------------:|-------------:|-------------:|---------:|--------:|-------:|----------:|------------:|
| Dictionary | 34.97 ns | 0.532 ns | 0.317 ns | 1.00 | 0.01 | - | - | NA |
| Runtime | 179.45 ns | 2.104 ns | 1.392 ns | 5.13 | 0.06 | 0.0153 | 200 B | NA |
| MsMemory | 75.12 ns | 0.338 ns | 0.201 ns | 2.15 | 0.02 | - | - | NA |
| Redis | 75,534.71 ns | 5,303.351 ns | 3,507.838 ns | 2,160.12 | 97.47 | 0.1221 | 2008 B | NA |


### Serializer comparison
Expand DownExpand Up@@ -149,12 +121,14 @@ Pretty simple but large enough to analyze the performance.
**Results:**


Method | Platform | Median | StdDev | Scaled | Scaled-SD |
-----: |:-------: |-----------: |----------: |-------: |-------: |
BinarySerializer | X64 | 62.4158 ms | 1.8668 ms | 2.20 | 0.07 |
JsonSerializer | X64 | 28.5521 ms | 0.4633 ms | 1.00 | 0.00 |
JsonGzSerializer | X64 | 102.5552 ms | 4.8584 ms | 3.60 | 0.18 |
ProtoBufSerializer | X64 | 11.1276 ms | 0.1252 ms | 0.39 | 0.01 |
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio |
|------------------------- |----------:|---------:|---------:|------:|--------:|--------:|-------:|----------:|------------:|
| JsonSerializer | 83.21 us | 1.163 us | 0.769 us | 1.00 | 0.01 | 14.8926 | 2.4414 | 191.16 KB | 1.00 |
| JsonGzSerializer | 346.37 us | 4.785 us | 3.165 us | 4.16 | 0.05 | 21.9727 | 2.9297 | 280.75 KB | 1.47 |
| ProtoBufSerializer | 39.18 us | 0.701 us | 0.463 us | 0.47 | 0.01 | 8.6060 | 1.0986 | 110.7 KB | 0.58 |
| BondBinarySerializer | 19.03 us | 0.398 us | 0.263 us | 0.23 | 0.00 | 4.7302 | 0.6714 | 60.53 KB | 0.32 |
| BondFastBinarySerializer | 19.42 us | 0.431 us | 0.285 us | 0.23 | 0.00 | 4.7607 | 0.7324 | 60.84 KB | 0.32 |
| BondSimpleJsonSerializer | 63.56 us | 1.132 us | 0.748 us | 0.76 | 0.01 | 11.9629 | 1.9531 | 153.35 KB | 0.80 |


As expected the protobuf serialization outperforms everything else by a huge margin!
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
<PackageReference Include="Microsoft.Garnet" Version="1.0.57" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
Expand Down
62 changes: 41 additions & 21 deletions benchmarks/CacheManager.Benchmarks/Program.cs
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,53 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Reflection;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;
using Garnet;
using Garnet.server;
using Microsoft.Extensions.Logging;

namespace CacheManager.Benchmarks
namespace CacheManager.Benchmarks;

[ExcludeFromCodeCoverage]
public class Program
{
[ExcludeFromCodeCoverage]
public class Program
public static GarnetServer StartServer(ILoggerFactory loggerFactory = null)
{
var server = new GarnetServer(new GarnetServerOptions()
{
EnableLua = true,
LuaOptions = new LuaOptions(LuaMemoryManagementMode.Native, string.Empty, TimeSpan.FromSeconds(5)),
EndPoint = new IPEndPoint(IPAddress.Loopback, 6379)
},
loggerFactory: loggerFactory);

server.Start();

return server;
}

public static void Main(string[] args)
{
public static void Main(string[] args)
StartServer();

do
{
do
{
var config = ManualConfig.CreateMinimumViable()
.AddJob(Job.Default
.WithIterationCount(10)
.WithWarmupCount(2)
.WithLaunchCount(1))
.AddDiagnoser(BenchmarkDotNet.Diagnosers.MemoryDiagnoser.Default);

BenchmarkSwitcher
.FromAssembly(typeof(Program).GetTypeInfo().Assembly)
.Run(args, config);

Console.WriteLine("done!");
Console.WriteLine("Press escape to exit or any key to continue...");
} while (Console.ReadKey().Key != ConsoleKey.Escape);
}
var config = ManualConfig.CreateMinimumViable()
.AddJob(Job.Default
.WithIterationCount(10)
.WithWarmupCount(2)
.WithLaunchCount(1))
.AddDiagnoser(BenchmarkDotNet.Diagnosers.MemoryDiagnoser.Default);

BenchmarkSwitcher
.FromAssembly(typeof(Program).GetTypeInfo().Assembly)
.Run(args, config);

Console.WriteLine("done!");
Console.WriteLine("Press escape to exit or any key to continue...");
} while (Console.ReadKey().Key != ConsoleKey.Escape);
}
}
14 changes: 0 additions & 14 deletions samples/OutputCacheExample/App_Start/BundleConfig.cs

This file was deleted.

13 changes: 0 additions & 13 deletions samples/OutputCacheExample/App_Start/FilterConfig.cs

This file was deleted.

23 changes: 0 additions & 23 deletions samples/OutputCacheExample/App_Start/RouteConfig.cs

This file was deleted.

24 changes: 0 additions & 24 deletions samples/OutputCacheExample/Content/Site.css

This file was deleted.

70 changes: 0 additions & 70 deletions samples/OutputCacheExample/Controllers/HomeController.cs

This file was deleted.

1 change: 0 additions & 1 deletion samples/OutputCacheExample/Global.asax

This file was deleted.

Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
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
110 changes: 42 additions & 68 deletions Benchmarks.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,15 +10,13 @@ All CacheManager instances used in the benchmarks have only one cache handle con
We are using the same configuration for all benchmarks and running two jobs each, one for x86 and one for x64. Regarding the different platforms, the conclusion is obviously that x64 is always faster than the x86 platform, but x64 consumes slightly more memory of course.

```ini
BenchmarkDotNet=v0.9.1.0
OS=Microsoft Windows NT 6.2.9200.0
Processor=Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz, ProcessorCount=8
Frequency=3328117 ticks, Resolution=300.4702 ns
HostCLR=MS.NET 4.0.30319.42000, Arch=32-bit RELEASE

Type=PutWithRegionSingleBenchmark Mode=Throughput Platform=X64
Jit=RyuJit LaunchCount=1 WarmupCount=2
TargetCount=100
BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.3194)
12th Gen Intel Core i7-12700KF, 1 CPU, 20 logical and 12 physical cores
.NET SDK 9.0.200
[Host] : .NET 8.0.13 (8.0.1325.6609), X64 RyuJIT AVX2
Job-BIBDFC : .NET 8.0.13 (8.0.1325.6609), X64 RyuJIT AVX2

IterationCount=10 LaunchCount=1 WarmupCount=2
```

### Add
Expand All@@ -27,73 +25,47 @@ TargetCount=100
Redis will be a lot slower in this scenario because CacheManager waits for the response to be able to return the `bool` value if the key has been added or not.
In general, it is good to see how fast the Dictionary handle is compared to the `System.Runtime` one. One thing you cannot see here is that also the memory footprint of the Dictionary handle is much lower.

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 1.7254 us | 0.0511 us | 1.00 |
Dictionary | X86 | 1.9563 us | 0.0399 us | 1.00 |
Runtime | X64 | 4.9839 us | 0.0778 us | 2.89 |
Runtime | X86 | 7.0324 us | 0.2012 us | 3.59 |
Redis | X64 | 56.6671 us | 1.7202 us | 32.84 |
Redis | X86 | 58.0775 us | 0.9517 us | 29.69 |

| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio |
|----------- |-------------:|------------:|------------:|-------:|--------:|-------:|-------:|----------:|------------:|
| Dictionary | 121.0 ns | 2.00 ns | 1.04 ns | 1.00 | 0.01 | 0.0153 | - | 200 B | 1.00 |
| Runtime | 637.2 ns | 28.46 ns | 16.94 ns | 5.27 | 0.14 | 0.2384 | 0.0010 | 3120 B | 15.60 |
| MsMemory | 198.8 ns | 4.59 ns | 3.04 ns | 1.64 | 0.03 | 0.0260 | - | 340 B | 1.70 |
| Redis | 105,395.6 ns | 3,758.09 ns | 2,485.74 ns | 871.20 | 20.86 | - | - | 1256 B | 6.28 |

*Adding one item per run with using region*

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 1.8094 us | 0.0386 us | 1.00 |
Dictionary | X86 | 2.5029 us | 0.1179 us | 1.00 |
Runtime | X64 | 6.6934 us | 0.1275 us | 3.70 |
Runtime | X86 | 9.2334 us | 0.1637 us | 3.69 |
Redis | X64 | 58.5355 us | 1.7054 us | 32.35 |
Redis | X86 | 61.0272 us | 1.4178 us | 24.38 |
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
|----------- |-------------:|------------:|------------:|-------:|--------:|-------:|----------:|------------:|
| Dictionary | 144.2 ns | 4.65 ns | 3.07 ns | 1.00 | 0.03 | 0.0231 | 304 B | 1.00 |
| Runtime | 342.6 ns | 18.01 ns | 11.91 ns | 2.38 | 0.09 | 0.0610 | 800 B | 2.63 |
| MsMemory | 164.1 ns | 5.74 ns | 3.80 ns | 1.14 | 0.03 | 0.0231 | 304 B | 1.00 |
| Redis | 139,200.0 ns | 4,863.92 ns | 2,894.44 ns | 966.01 | 27.25 | - | 1528 B | 5.03 |


### Put
*Put 1 item per run*
Redis is as fast as the other handles in this scenario because CacheManager uses fire and forget for those operations. For Put it doesn't matter to know if the item has been added or updated...

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 1.6802 us | 0.0235 us | 1.00 |
Dictionary | X86 | 1.9445 us | 0.0341 us | 1.00 |
Runtime | X64 | 4.4431 us | 0.0651 us | 2.64 |
Runtime | X86 | 6.5231 us | 0.1063 us | 3.35 |
Redis | X64 | 2.6869 us | 0.0934 us | 1.60 |
Redis | X86 | 3.5490 us | 0.0848 us | 1.83 |

*Put 1 item per run with region*

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 1.7401 us | 0.0365 us | 1.00 |
Dictionary | X86 | 2.4589 us | 0.1022 us | 1.00 |
Runtime | X64 | 6.1772 us | 0.3683 us | 3.55 |
Runtime | X86 | 9.9298 us | 0.5574 us | 4.04 |
Redis | X64 | 3.0807 us | 0.0906 us | 1.77 |
Redis | X86 | 3.6385 us | 0.2123 us | 1.48 |
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio |
|----------- |------------:|-----------:|-----------:|------:|--------:|-------:|-------:|----------:|------------:|
| Dictionary | 95.01 ns | 1.888 ns | 1.249 ns | 1.00 | 0.02 | 0.0122 | - | 160 B | 1.00 |
| Runtime | 887.35 ns | 19.929 ns | 13.181 ns | 9.34 | 0.18 | 0.4263 | 0.0095 | 5576 B | 34.85 |
| MsMemory | 172.40 ns | 5.030 ns | 3.327 ns | 1.81 | 0.04 | 0.0336 | - | 440 B | 2.75 |
| Redis | 4,136.37 ns | 301.420 ns | 199.371 ns | 43.54 | 2.07 | 0.0839 | 0.0610 | 1095 B | 6.84 |


### Get
*Get 1 item per run*
With `Get` operations we can clearly see how much faster an in-memory cache is, compared to the distributed variant. That's why it makes so much sense to use CacheManager with a first and secondary cache layer.

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 169.8708 ns | 4.6186 ns | 1.00 |
Dictionary | X86 | 540.0879 ns | 8.9363 ns | 1.00 |
Runtime | X64 | 310.4025 ns | 8.2928 ns | 1.83 |
Runtime | X86 | 1,030.5911 ns | 8.0532 ns | 1.91 |
Redis | X64 | 56,917.3537 ns | 2,035.4765 ns | 335.06 |
Redis | X86 | 59,519.5459 ns | 1,527.9613 ns | 110.20 |

*Get 1 item per run with region*

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 234.9676 ns | 7.9172 ns | 1.00 |
Dictionary | X86 | 600.0429 ns | 13.7090 ns | 1.00 |
Runtime | X64 | 497.4116 ns | 15.6998 ns | 2.12 |
Runtime | X86 | 1,191.6452 ns | 19.8484 ns | 1.99 |
Redis | X64 | 57,257.9868 ns | 1,705.0148 ns | 243.68 |
Redis | X86 | 61,789.3944 ns | 1,775.6064 ns | 102.97 |
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
|----------- |-------------:|-------------:|-------------:|---------:|--------:|-------:|----------:|------------:|
| Dictionary | 34.97 ns | 0.532 ns | 0.317 ns | 1.00 | 0.01 | - | - | NA |
| Runtime | 179.45 ns | 2.104 ns | 1.392 ns | 5.13 | 0.06 | 0.0153 | 200 B | NA |
| MsMemory | 75.12 ns | 0.338 ns | 0.201 ns | 2.15 | 0.02 | - | - | NA |
| Redis | 75,534.71 ns | 5,303.351 ns | 3,507.838 ns | 2,160.12 | 97.47 | 0.1221 | 2008 B | NA |


### Serializer comparison
Expand DownExpand Up@@ -149,12 +121,14 @@ Pretty simple but large enough to analyze the performance.
**Results:**


Method | Platform | Median | StdDev | Scaled | Scaled-SD |
-----: |:-------: |-----------: |----------: |-------: |-------: |
BinarySerializer | X64 | 62.4158 ms | 1.8668 ms | 2.20 | 0.07 |
JsonSerializer | X64 | 28.5521 ms | 0.4633 ms | 1.00 | 0.00 |
JsonGzSerializer | X64 | 102.5552 ms | 4.8584 ms | 3.60 | 0.18 |
ProtoBufSerializer | X64 | 11.1276 ms | 0.1252 ms | 0.39 | 0.01 |
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio |
|------------------------- |----------:|---------:|---------:|------:|--------:|--------:|-------:|----------:|------------:|
| JsonSerializer | 83.21 us | 1.163 us | 0.769 us | 1.00 | 0.01 | 14.8926 | 2.4414 | 191.16 KB | 1.00 |
| JsonGzSerializer | 346.37 us | 4.785 us | 3.165 us | 4.16 | 0.05 | 21.9727 | 2.9297 | 280.75 KB | 1.47 |
| ProtoBufSerializer | 39.18 us | 0.701 us | 0.463 us | 0.47 | 0.01 | 8.6060 | 1.0986 | 110.7 KB | 0.58 |
| BondBinarySerializer | 19.03 us | 0.398 us | 0.263 us | 0.23 | 0.00 | 4.7302 | 0.6714 | 60.53 KB | 0.32 |
| BondFastBinarySerializer | 19.42 us | 0.431 us | 0.285 us | 0.23 | 0.00 | 4.7607 | 0.7324 | 60.84 KB | 0.32 |
| BondSimpleJsonSerializer | 63.56 us | 1.132 us | 0.748 us | 0.76 | 0.01 | 11.9629 | 1.9531 | 153.35 KB | 0.80 |


As expected the protobuf serialization outperforms everything else by a huge margin!
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
<PackageReference Include="Microsoft.Garnet" Version="1.0.57" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
Expand Down
62 changes: 41 additions & 21 deletions benchmarks/CacheManager.Benchmarks/Program.cs
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,53 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Reflection;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;
using Garnet;
using Garnet.server;
using Microsoft.Extensions.Logging;

namespace CacheManager.Benchmarks
namespace CacheManager.Benchmarks;

[ExcludeFromCodeCoverage]
public class Program
{
[ExcludeFromCodeCoverage]
public class Program
public static GarnetServer StartServer(ILoggerFactory loggerFactory = null)
{
var server = new GarnetServer(new GarnetServerOptions()
{
EnableLua = true,
LuaOptions = new LuaOptions(LuaMemoryManagementMode.Native, string.Empty, TimeSpan.FromSeconds(5)),
EndPoint = new IPEndPoint(IPAddress.Loopback, 6379)
},
loggerFactory: loggerFactory);

server.Start();

return server;
}

public static void Main(string[] args)
{
public static void Main(string[] args)
StartServer();

do
{
do
{
var config = ManualConfig.CreateMinimumViable()
.AddJob(Job.Default
.WithIterationCount(10)
.WithWarmupCount(2)
.WithLaunchCount(1))
.AddDiagnoser(BenchmarkDotNet.Diagnosers.MemoryDiagnoser.Default);

BenchmarkSwitcher
.FromAssembly(typeof(Program).GetTypeInfo().Assembly)
.Run(args, config);

Console.WriteLine("done!");
Console.WriteLine("Press escape to exit or any key to continue...");
} while (Console.ReadKey().Key != ConsoleKey.Escape);
}
var config = ManualConfig.CreateMinimumViable()
.AddJob(Job.Default
.WithIterationCount(10)
.WithWarmupCount(2)
.WithLaunchCount(1))
.AddDiagnoser(BenchmarkDotNet.Diagnosers.MemoryDiagnoser.Default);

BenchmarkSwitcher
.FromAssembly(typeof(Program).GetTypeInfo().Assembly)
.Run(args, config);

Console.WriteLine("done!");
Console.WriteLine("Press escape to exit or any key to continue...");
} while (Console.ReadKey().Key != ConsoleKey.Escape);
}
}
14 changes: 0 additions & 14 deletions samples/OutputCacheExample/App_Start/BundleConfig.cs

This file was deleted.

13 changes: 0 additions & 13 deletions samples/OutputCacheExample/App_Start/FilterConfig.cs

This file was deleted.

23 changes: 0 additions & 23 deletions samples/OutputCacheExample/App_Start/RouteConfig.cs

This file was deleted.

24 changes: 0 additions & 24 deletions samples/OutputCacheExample/Content/Site.css

This file was deleted.

70 changes: 0 additions & 70 deletions samples/OutputCacheExample/Controllers/HomeController.cs

This file was deleted.

1 change: 0 additions & 1 deletion samples/OutputCacheExample/Global.asax

This file was deleted.

Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
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
110 changes: 42 additions & 68 deletions Benchmarks.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,15 +10,13 @@ All CacheManager instances used in the benchmarks have only one cache handle con
We are using the same configuration for all benchmarks and running two jobs each, one for x86 and one for x64. Regarding the different platforms, the conclusion is obviously that x64 is always faster than the x86 platform, but x64 consumes slightly more memory of course.

```ini
BenchmarkDotNet=v0.9.1.0
OS=Microsoft Windows NT 6.2.9200.0
Processor=Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz, ProcessorCount=8
Frequency=3328117 ticks, Resolution=300.4702 ns
HostCLR=MS.NET 4.0.30319.42000, Arch=32-bit RELEASE

Type=PutWithRegionSingleBenchmark Mode=Throughput Platform=X64
Jit=RyuJit LaunchCount=1 WarmupCount=2
TargetCount=100
BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.3194)
12th Gen Intel Core i7-12700KF, 1 CPU, 20 logical and 12 physical cores
.NET SDK 9.0.200
[Host] : .NET 8.0.13 (8.0.1325.6609), X64 RyuJIT AVX2
Job-BIBDFC : .NET 8.0.13 (8.0.1325.6609), X64 RyuJIT AVX2

IterationCount=10 LaunchCount=1 WarmupCount=2
```

### Add
Expand All@@ -27,73 +25,47 @@ TargetCount=100
Redis will be a lot slower in this scenario because CacheManager waits for the response to be able to return the `bool` value if the key has been added or not.
In general, it is good to see how fast the Dictionary handle is compared to the `System.Runtime` one. One thing you cannot see here is that also the memory footprint of the Dictionary handle is much lower.

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 1.7254 us | 0.0511 us | 1.00 |
Dictionary | X86 | 1.9563 us | 0.0399 us | 1.00 |
Runtime | X64 | 4.9839 us | 0.0778 us | 2.89 |
Runtime | X86 | 7.0324 us | 0.2012 us | 3.59 |
Redis | X64 | 56.6671 us | 1.7202 us | 32.84 |
Redis | X86 | 58.0775 us | 0.9517 us | 29.69 |

| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio |
|----------- |-------------:|------------:|------------:|-------:|--------:|-------:|-------:|----------:|------------:|
| Dictionary | 121.0 ns | 2.00 ns | 1.04 ns | 1.00 | 0.01 | 0.0153 | - | 200 B | 1.00 |
| Runtime | 637.2 ns | 28.46 ns | 16.94 ns | 5.27 | 0.14 | 0.2384 | 0.0010 | 3120 B | 15.60 |
| MsMemory | 198.8 ns | 4.59 ns | 3.04 ns | 1.64 | 0.03 | 0.0260 | - | 340 B | 1.70 |
| Redis | 105,395.6 ns | 3,758.09 ns | 2,485.74 ns | 871.20 | 20.86 | - | - | 1256 B | 6.28 |

*Adding one item per run with using region*

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 1.8094 us | 0.0386 us | 1.00 |
Dictionary | X86 | 2.5029 us | 0.1179 us | 1.00 |
Runtime | X64 | 6.6934 us | 0.1275 us | 3.70 |
Runtime | X86 | 9.2334 us | 0.1637 us | 3.69 |
Redis | X64 | 58.5355 us | 1.7054 us | 32.35 |
Redis | X86 | 61.0272 us | 1.4178 us | 24.38 |
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
|----------- |-------------:|------------:|------------:|-------:|--------:|-------:|----------:|------------:|
| Dictionary | 144.2 ns | 4.65 ns | 3.07 ns | 1.00 | 0.03 | 0.0231 | 304 B | 1.00 |
| Runtime | 342.6 ns | 18.01 ns | 11.91 ns | 2.38 | 0.09 | 0.0610 | 800 B | 2.63 |
| MsMemory | 164.1 ns | 5.74 ns | 3.80 ns | 1.14 | 0.03 | 0.0231 | 304 B | 1.00 |
| Redis | 139,200.0 ns | 4,863.92 ns | 2,894.44 ns | 966.01 | 27.25 | - | 1528 B | 5.03 |


### Put
*Put 1 item per run*
Redis is as fast as the other handles in this scenario because CacheManager uses fire and forget for those operations. For Put it doesn't matter to know if the item has been added or updated...

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 1.6802 us | 0.0235 us | 1.00 |
Dictionary | X86 | 1.9445 us | 0.0341 us | 1.00 |
Runtime | X64 | 4.4431 us | 0.0651 us | 2.64 |
Runtime | X86 | 6.5231 us | 0.1063 us | 3.35 |
Redis | X64 | 2.6869 us | 0.0934 us | 1.60 |
Redis | X86 | 3.5490 us | 0.0848 us | 1.83 |

*Put 1 item per run with region*

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 1.7401 us | 0.0365 us | 1.00 |
Dictionary | X86 | 2.4589 us | 0.1022 us | 1.00 |
Runtime | X64 | 6.1772 us | 0.3683 us | 3.55 |
Runtime | X86 | 9.9298 us | 0.5574 us | 4.04 |
Redis | X64 | 3.0807 us | 0.0906 us | 1.77 |
Redis | X86 | 3.6385 us | 0.2123 us | 1.48 |
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio |
|----------- |------------:|-----------:|-----------:|------:|--------:|-------:|-------:|----------:|------------:|
| Dictionary | 95.01 ns | 1.888 ns | 1.249 ns | 1.00 | 0.02 | 0.0122 | - | 160 B | 1.00 |
| Runtime | 887.35 ns | 19.929 ns | 13.181 ns | 9.34 | 0.18 | 0.4263 | 0.0095 | 5576 B | 34.85 |
| MsMemory | 172.40 ns | 5.030 ns | 3.327 ns | 1.81 | 0.04 | 0.0336 | - | 440 B | 2.75 |
| Redis | 4,136.37 ns | 301.420 ns | 199.371 ns | 43.54 | 2.07 | 0.0839 | 0.0610 | 1095 B | 6.84 |


### Get
*Get 1 item per run*
With `Get` operations we can clearly see how much faster an in-memory cache is, compared to the distributed variant. That's why it makes so much sense to use CacheManager with a first and secondary cache layer.

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 169.8708 ns | 4.6186 ns | 1.00 |
Dictionary | X86 | 540.0879 ns | 8.9363 ns | 1.00 |
Runtime | X64 | 310.4025 ns | 8.2928 ns | 1.83 |
Runtime | X86 | 1,030.5911 ns | 8.0532 ns | 1.91 |
Redis | X64 | 56,917.3537 ns | 2,035.4765 ns | 335.06 |
Redis | X86 | 59,519.5459 ns | 1,527.9613 ns | 110.20 |

*Get 1 item per run with region*

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 234.9676 ns | 7.9172 ns | 1.00 |
Dictionary | X86 | 600.0429 ns | 13.7090 ns | 1.00 |
Runtime | X64 | 497.4116 ns | 15.6998 ns | 2.12 |
Runtime | X86 | 1,191.6452 ns | 19.8484 ns | 1.99 |
Redis | X64 | 57,257.9868 ns | 1,705.0148 ns | 243.68 |
Redis | X86 | 61,789.3944 ns | 1,775.6064 ns | 102.97 |
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
|----------- |-------------:|-------------:|-------------:|---------:|--------:|-------:|----------:|------------:|
| Dictionary | 34.97 ns | 0.532 ns | 0.317 ns | 1.00 | 0.01 | - | - | NA |
| Runtime | 179.45 ns | 2.104 ns | 1.392 ns | 5.13 | 0.06 | 0.0153 | 200 B | NA |
| MsMemory | 75.12 ns | 0.338 ns | 0.201 ns | 2.15 | 0.02 | - | - | NA |
| Redis | 75,534.71 ns | 5,303.351 ns | 3,507.838 ns | 2,160.12 | 97.47 | 0.1221 | 2008 B | NA |


### Serializer comparison
Expand DownExpand Up@@ -149,12 +121,14 @@ Pretty simple but large enough to analyze the performance.
**Results:**


Method | Platform | Median | StdDev | Scaled | Scaled-SD |
-----: |:-------: |-----------: |----------: |-------: |-------: |
BinarySerializer | X64 | 62.4158 ms | 1.8668 ms | 2.20 | 0.07 |
JsonSerializer | X64 | 28.5521 ms | 0.4633 ms | 1.00 | 0.00 |
JsonGzSerializer | X64 | 102.5552 ms | 4.8584 ms | 3.60 | 0.18 |
ProtoBufSerializer | X64 | 11.1276 ms | 0.1252 ms | 0.39 | 0.01 |
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio |
|------------------------- |----------:|---------:|---------:|------:|--------:|--------:|-------:|----------:|------------:|
| JsonSerializer | 83.21 us | 1.163 us | 0.769 us | 1.00 | 0.01 | 14.8926 | 2.4414 | 191.16 KB | 1.00 |
| JsonGzSerializer | 346.37 us | 4.785 us | 3.165 us | 4.16 | 0.05 | 21.9727 | 2.9297 | 280.75 KB | 1.47 |
| ProtoBufSerializer | 39.18 us | 0.701 us | 0.463 us | 0.47 | 0.01 | 8.6060 | 1.0986 | 110.7 KB | 0.58 |
| BondBinarySerializer | 19.03 us | 0.398 us | 0.263 us | 0.23 | 0.00 | 4.7302 | 0.6714 | 60.53 KB | 0.32 |
| BondFastBinarySerializer | 19.42 us | 0.431 us | 0.285 us | 0.23 | 0.00 | 4.7607 | 0.7324 | 60.84 KB | 0.32 |
| BondSimpleJsonSerializer | 63.56 us | 1.132 us | 0.748 us | 0.76 | 0.01 | 11.9629 | 1.9531 | 153.35 KB | 0.80 |


As expected the protobuf serialization outperforms everything else by a huge margin!
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
<PackageReference Include="Microsoft.Garnet" Version="1.0.57" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
Expand Down
62 changes: 41 additions & 21 deletions benchmarks/CacheManager.Benchmarks/Program.cs
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,53 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Reflection;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;
using Garnet;
using Garnet.server;
using Microsoft.Extensions.Logging;

namespace CacheManager.Benchmarks
namespace CacheManager.Benchmarks;

[ExcludeFromCodeCoverage]
public class Program
{
[ExcludeFromCodeCoverage]
public class Program
public static GarnetServer StartServer(ILoggerFactory loggerFactory = null)
{
var server = new GarnetServer(new GarnetServerOptions()
{
EnableLua = true,
LuaOptions = new LuaOptions(LuaMemoryManagementMode.Native, string.Empty, TimeSpan.FromSeconds(5)),
EndPoint = new IPEndPoint(IPAddress.Loopback, 6379)
},
loggerFactory: loggerFactory);

server.Start();

return server;
}

public static void Main(string[] args)
{
public static void Main(string[] args)
StartServer();

do
{
do
{
var config = ManualConfig.CreateMinimumViable()
.AddJob(Job.Default
.WithIterationCount(10)
.WithWarmupCount(2)
.WithLaunchCount(1))
.AddDiagnoser(BenchmarkDotNet.Diagnosers.MemoryDiagnoser.Default);

BenchmarkSwitcher
.FromAssembly(typeof(Program).GetTypeInfo().Assembly)
.Run(args, config);

Console.WriteLine("done!");
Console.WriteLine("Press escape to exit or any key to continue...");
} while (Console.ReadKey().Key != ConsoleKey.Escape);
}
var config = ManualConfig.CreateMinimumViable()
.AddJob(Job.Default
.WithIterationCount(10)
.WithWarmupCount(2)
.WithLaunchCount(1))
.AddDiagnoser(BenchmarkDotNet.Diagnosers.MemoryDiagnoser.Default);

BenchmarkSwitcher
.FromAssembly(typeof(Program).GetTypeInfo().Assembly)
.Run(args, config);

Console.WriteLine("done!");
Console.WriteLine("Press escape to exit or any key to continue...");
} while (Console.ReadKey().Key != ConsoleKey.Escape);
}
}
14 changes: 0 additions & 14 deletions samples/OutputCacheExample/App_Start/BundleConfig.cs

This file was deleted.

13 changes: 0 additions & 13 deletions samples/OutputCacheExample/App_Start/FilterConfig.cs

This file was deleted.

23 changes: 0 additions & 23 deletions samples/OutputCacheExample/App_Start/RouteConfig.cs

This file was deleted.

24 changes: 0 additions & 24 deletions samples/OutputCacheExample/Content/Site.css

This file was deleted.

70 changes: 0 additions & 70 deletions samples/OutputCacheExample/Controllers/HomeController.cs

This file was deleted.

1 change: 0 additions & 1 deletion samples/OutputCacheExample/Global.asax

This file was deleted.

Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
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
110 changes: 42 additions & 68 deletions Benchmarks.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,15 +10,13 @@ All CacheManager instances used in the benchmarks have only one cache handle con
We are using the same configuration for all benchmarks and running two jobs each, one for x86 and one for x64. Regarding the different platforms, the conclusion is obviously that x64 is always faster than the x86 platform, but x64 consumes slightly more memory of course.

```ini
BenchmarkDotNet=v0.9.1.0
OS=Microsoft Windows NT 6.2.9200.0
Processor=Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz, ProcessorCount=8
Frequency=3328117 ticks, Resolution=300.4702 ns
HostCLR=MS.NET 4.0.30319.42000, Arch=32-bit RELEASE

Type=PutWithRegionSingleBenchmark Mode=Throughput Platform=X64
Jit=RyuJit LaunchCount=1 WarmupCount=2
TargetCount=100
BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.3194)
12th Gen Intel Core i7-12700KF, 1 CPU, 20 logical and 12 physical cores
.NET SDK 9.0.200
[Host] : .NET 8.0.13 (8.0.1325.6609), X64 RyuJIT AVX2
Job-BIBDFC : .NET 8.0.13 (8.0.1325.6609), X64 RyuJIT AVX2

IterationCount=10 LaunchCount=1 WarmupCount=2
```

### Add
Expand All@@ -27,73 +25,47 @@ TargetCount=100
Redis will be a lot slower in this scenario because CacheManager waits for the response to be able to return the `bool` value if the key has been added or not.
In general, it is good to see how fast the Dictionary handle is compared to the `System.Runtime` one. One thing you cannot see here is that also the memory footprint of the Dictionary handle is much lower.

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 1.7254 us | 0.0511 us | 1.00 |
Dictionary | X86 | 1.9563 us | 0.0399 us | 1.00 |
Runtime | X64 | 4.9839 us | 0.0778 us | 2.89 |
Runtime | X86 | 7.0324 us | 0.2012 us | 3.59 |
Redis | X64 | 56.6671 us | 1.7202 us | 32.84 |
Redis | X86 | 58.0775 us | 0.9517 us | 29.69 |

| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio |
|----------- |-------------:|------------:|------------:|-------:|--------:|-------:|-------:|----------:|------------:|
| Dictionary | 121.0 ns | 2.00 ns | 1.04 ns | 1.00 | 0.01 | 0.0153 | - | 200 B | 1.00 |
| Runtime | 637.2 ns | 28.46 ns | 16.94 ns | 5.27 | 0.14 | 0.2384 | 0.0010 | 3120 B | 15.60 |
| MsMemory | 198.8 ns | 4.59 ns | 3.04 ns | 1.64 | 0.03 | 0.0260 | - | 340 B | 1.70 |
| Redis | 105,395.6 ns | 3,758.09 ns | 2,485.74 ns | 871.20 | 20.86 | - | - | 1256 B | 6.28 |

*Adding one item per run with using region*

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 1.8094 us | 0.0386 us | 1.00 |
Dictionary | X86 | 2.5029 us | 0.1179 us | 1.00 |
Runtime | X64 | 6.6934 us | 0.1275 us | 3.70 |
Runtime | X86 | 9.2334 us | 0.1637 us | 3.69 |
Redis | X64 | 58.5355 us | 1.7054 us | 32.35 |
Redis | X86 | 61.0272 us | 1.4178 us | 24.38 |
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
|----------- |-------------:|------------:|------------:|-------:|--------:|-------:|----------:|------------:|
| Dictionary | 144.2 ns | 4.65 ns | 3.07 ns | 1.00 | 0.03 | 0.0231 | 304 B | 1.00 |
| Runtime | 342.6 ns | 18.01 ns | 11.91 ns | 2.38 | 0.09 | 0.0610 | 800 B | 2.63 |
| MsMemory | 164.1 ns | 5.74 ns | 3.80 ns | 1.14 | 0.03 | 0.0231 | 304 B | 1.00 |
| Redis | 139,200.0 ns | 4,863.92 ns | 2,894.44 ns | 966.01 | 27.25 | - | 1528 B | 5.03 |


### Put
*Put 1 item per run*
Redis is as fast as the other handles in this scenario because CacheManager uses fire and forget for those operations. For Put it doesn't matter to know if the item has been added or updated...

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 1.6802 us | 0.0235 us | 1.00 |
Dictionary | X86 | 1.9445 us | 0.0341 us | 1.00 |
Runtime | X64 | 4.4431 us | 0.0651 us | 2.64 |
Runtime | X86 | 6.5231 us | 0.1063 us | 3.35 |
Redis | X64 | 2.6869 us | 0.0934 us | 1.60 |
Redis | X86 | 3.5490 us | 0.0848 us | 1.83 |

*Put 1 item per run with region*

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 1.7401 us | 0.0365 us | 1.00 |
Dictionary | X86 | 2.4589 us | 0.1022 us | 1.00 |
Runtime | X64 | 6.1772 us | 0.3683 us | 3.55 |
Runtime | X86 | 9.9298 us | 0.5574 us | 4.04 |
Redis | X64 | 3.0807 us | 0.0906 us | 1.77 |
Redis | X86 | 3.6385 us | 0.2123 us | 1.48 |
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio |
|----------- |------------:|-----------:|-----------:|------:|--------:|-------:|-------:|----------:|------------:|
| Dictionary | 95.01 ns | 1.888 ns | 1.249 ns | 1.00 | 0.02 | 0.0122 | - | 160 B | 1.00 |
| Runtime | 887.35 ns | 19.929 ns | 13.181 ns | 9.34 | 0.18 | 0.4263 | 0.0095 | 5576 B | 34.85 |
| MsMemory | 172.40 ns | 5.030 ns | 3.327 ns | 1.81 | 0.04 | 0.0336 | - | 440 B | 2.75 |
| Redis | 4,136.37 ns | 301.420 ns | 199.371 ns | 43.54 | 2.07 | 0.0839 | 0.0610 | 1095 B | 6.84 |


### Get
*Get 1 item per run*
With `Get` operations we can clearly see how much faster an in-memory cache is, compared to the distributed variant. That's why it makes so much sense to use CacheManager with a first and secondary cache layer.

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 169.8708 ns | 4.6186 ns | 1.00 |
Dictionary | X86 | 540.0879 ns | 8.9363 ns | 1.00 |
Runtime | X64 | 310.4025 ns | 8.2928 ns | 1.83 |
Runtime | X86 | 1,030.5911 ns | 8.0532 ns | 1.91 |
Redis | X64 | 56,917.3537 ns | 2,035.4765 ns | 335.06 |
Redis | X86 | 59,519.5459 ns | 1,527.9613 ns | 110.20 |

*Get 1 item per run with region*

Method | Platform | Median | StdDev | Scaled |
-----------: |:-----------: |-----------: |----------: |-------: |
Dictionary | X64 | 234.9676 ns | 7.9172 ns | 1.00 |
Dictionary | X86 | 600.0429 ns | 13.7090 ns | 1.00 |
Runtime | X64 | 497.4116 ns | 15.6998 ns | 2.12 |
Runtime | X86 | 1,191.6452 ns | 19.8484 ns | 1.99 |
Redis | X64 | 57,257.9868 ns | 1,705.0148 ns | 243.68 |
Redis | X86 | 61,789.3944 ns | 1,775.6064 ns | 102.97 |
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
|----------- |-------------:|-------------:|-------------:|---------:|--------:|-------:|----------:|------------:|
| Dictionary | 34.97 ns | 0.532 ns | 0.317 ns | 1.00 | 0.01 | - | - | NA |
| Runtime | 179.45 ns | 2.104 ns | 1.392 ns | 5.13 | 0.06 | 0.0153 | 200 B | NA |
| MsMemory | 75.12 ns | 0.338 ns | 0.201 ns | 2.15 | 0.02 | - | - | NA |
| Redis | 75,534.71 ns | 5,303.351 ns | 3,507.838 ns | 2,160.12 | 97.47 | 0.1221 | 2008 B | NA |


### Serializer comparison
Expand DownExpand Up@@ -149,12 +121,14 @@ Pretty simple but large enough to analyze the performance.
**Results:**


Method | Platform | Median | StdDev | Scaled | Scaled-SD |
-----: |:-------: |-----------: |----------: |-------: |-------: |
BinarySerializer | X64 | 62.4158 ms | 1.8668 ms | 2.20 | 0.07 |
JsonSerializer | X64 | 28.5521 ms | 0.4633 ms | 1.00 | 0.00 |
JsonGzSerializer | X64 | 102.5552 ms | 4.8584 ms | 3.60 | 0.18 |
ProtoBufSerializer | X64 | 11.1276 ms | 0.1252 ms | 0.39 | 0.01 |
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio |
|------------------------- |----------:|---------:|---------:|------:|--------:|--------:|-------:|----------:|------------:|
| JsonSerializer | 83.21 us | 1.163 us | 0.769 us | 1.00 | 0.01 | 14.8926 | 2.4414 | 191.16 KB | 1.00 |
| JsonGzSerializer | 346.37 us | 4.785 us | 3.165 us | 4.16 | 0.05 | 21.9727 | 2.9297 | 280.75 KB | 1.47 |
| ProtoBufSerializer | 39.18 us | 0.701 us | 0.463 us | 0.47 | 0.01 | 8.6060 | 1.0986 | 110.7 KB | 0.58 |
| BondBinarySerializer | 19.03 us | 0.398 us | 0.263 us | 0.23 | 0.00 | 4.7302 | 0.6714 | 60.53 KB | 0.32 |
| BondFastBinarySerializer | 19.42 us | 0.431 us | 0.285 us | 0.23 | 0.00 | 4.7607 | 0.7324 | 60.84 KB | 0.32 |
| BondSimpleJsonSerializer | 63.56 us | 1.132 us | 0.748 us | 0.76 | 0.01 | 11.9629 | 1.9531 | 153.35 KB | 0.80 |


As expected the protobuf serialization outperforms everything else by a huge margin!
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
<PackageReference Include="Microsoft.Garnet" Version="1.0.57" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
Expand Down
62 changes: 41 additions & 21 deletions benchmarks/CacheManager.Benchmarks/Program.cs
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,53 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Reflection;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;
using Garnet;
using Garnet.server;
using Microsoft.Extensions.Logging;

namespace CacheManager.Benchmarks
namespace CacheManager.Benchmarks;

[ExcludeFromCodeCoverage]
public class Program
{
[ExcludeFromCodeCoverage]
public class Program
public static GarnetServer StartServer(ILoggerFactory loggerFactory = null)
{
var server = new GarnetServer(new GarnetServerOptions()
{
EnableLua = true,
LuaOptions = new LuaOptions(LuaMemoryManagementMode.Native, string.Empty, TimeSpan.FromSeconds(5)),
EndPoint = new IPEndPoint(IPAddress.Loopback, 6379)
},
loggerFactory: loggerFactory);

server.Start();

return server;
}

public static void Main(string[] args)
{
public static void Main(string[] args)
StartServer();

do
{
do
{
var config = ManualConfig.CreateMinimumViable()
.AddJob(Job.Default
.WithIterationCount(10)
.WithWarmupCount(2)
.WithLaunchCount(1))
.AddDiagnoser(BenchmarkDotNet.Diagnosers.MemoryDiagnoser.Default);

BenchmarkSwitcher
.FromAssembly(typeof(Program).GetTypeInfo().Assembly)
.Run(args, config);

Console.WriteLine("done!");
Console.WriteLine("Press escape to exit or any key to continue...");
} while (Console.ReadKey().Key != ConsoleKey.Escape);
}
var config = ManualConfig.CreateMinimumViable()
.AddJob(Job.Default
.WithIterationCount(10)
.WithWarmupCount(2)
.WithLaunchCount(1))
.AddDiagnoser(BenchmarkDotNet.Diagnosers.MemoryDiagnoser.Default);

BenchmarkSwitcher
.FromAssembly(typeof(Program).GetTypeInfo().Assembly)
.Run(args, config);

Console.WriteLine("done!");
Console.WriteLine("Press escape to exit or any key to continue...");
} while (Console.ReadKey().Key != ConsoleKey.Escape);
}
}
14 changes: 0 additions & 14 deletions samples/OutputCacheExample/App_Start/BundleConfig.cs

This file was deleted.

13 changes: 0 additions & 13 deletions samples/OutputCacheExample/App_Start/FilterConfig.cs

This file was deleted.

23 changes: 0 additions & 23 deletions samples/OutputCacheExample/App_Start/RouteConfig.cs

This file was deleted.

24 changes: 0 additions & 24 deletions samples/OutputCacheExample/Content/Site.css

This file was deleted.

70 changes: 0 additions & 70 deletions samples/OutputCacheExample/Controllers/HomeController.cs

This file was deleted.

1 change: 0 additions & 1 deletion samples/OutputCacheExample/Global.asax

This file was deleted.

Loading