Skip to content

Instantly share code, notes, and snippets.

@Tyrrrz
Last active December 13, 2021 18:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Tyrrrz/8833223d85da662d827ac874cbf624cd to your computer and use it in GitHub Desktop.
Save Tyrrrz/8833223d85da662d827ac874cbf624cd to your computer and use it in GitHub Desktop.
Global cache in C#

// * Summary *

BenchmarkDotNet=v0.13.1, OS=Windows 10.0.19043.1348 (21H1/May2021Update) 11th Gen Intel Core i5-11600K 3.90GHz, 1 CPU, 12 logical and 6 physical cores .NET SDK=6.0.100 [Host] : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT DefaultJob : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT

Method Mean Error StdDev Ratio RatioSD Allocated
Uncached 108,480,368.000 ns 526,869.7917 ns 492,834.3301 ns 52,451,633.25 215,509.29 728 B
CachedThroughField 2.067 ns 0.0047 ns 0.0040 ns 1.00 0.00 -
CachedThroughLazy 1.552 ns 0.0075 ns 0.0070 ns 0.75 0.00 -
CachedThroughWeakReference 28.001 ns 0.1384 ns 0.1295 ns 13.54 0.07 -

// * Hints * Outliers Benchmarks.CachedThroughField: Default -> 2 outliers were removed (2.09 ns, 2.10 ns)

// * Legends * Mean : Arithmetic mean of all measurements Error : Half of 99.9% confidence interval StdDev : Standard deviation of all measurements Ratio : Mean of the ratio distribution ([Current]/[Baseline]) RatioSD : Standard deviation of the ratio distribution ([Current]/[Baseline]) Allocated : Allocated memory per single operation (managed only, inclusive, 1KB = 1024B) 1 ns : 1 Nanosecond (0.000000001 sec)

using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
BenchmarkRunner.Run<Benchmarks>();
internal static class Memo
{
private static class ForValue<T>
{
private static readonly ConditionalWeakTable<object, Dictionary<int, T>> CacheManifest = new();
public static Dictionary<int, T> GetCacheForOwner(object owner) =>
CacheManifest.GetOrCreateValue(owner);
}
public static T Cache<T>(object owner, Func<T> getValue)
{
var cache = ForValue<T>.GetCacheForOwner(owner);
var key = getValue.Method.GetHashCode();
if (cache.TryGetValue(key, out var cachedValue))
return cachedValue;
var value = getValue();
cache[key] = value;
return value;
}
}
static class Answer
{
public static int Compute()
{
Thread.Sleep(100);
return 42;
}
}
class Uncached
{
public int Prop => Answer.Compute();
}
class CachedThroughField
{
private int? _prop;
public int Prop => _prop ??= Answer.Compute();
}
class CachedThroughLazy
{
private readonly Lazy<int> _propLazy = new(() => Answer.Compute());
public int Prop => _propLazy.Value;
}
class CachedThroughWeakReference
{
public int Prop => Memo.Cache(this, () => Answer.Compute());
}
[MemoryDiagnoser]
public class Benchmarks
{
private readonly Uncached _uncached = new();
private readonly CachedThroughField _cachedThroughField = new();
private readonly CachedThroughLazy _cachedThroughLazy = new();
private readonly CachedThroughWeakReference _cachedThroughWeakReference = new();
[Benchmark]
public int Uncached() => _uncached.Prop;
[Benchmark(Baseline = true)]
public int CachedThroughField() => _cachedThroughField.Prop;
[Benchmark]
public int CachedThroughLazy() => _cachedThroughLazy.Prop;
[Benchmark]
public int CachedThroughWeakReference() => _cachedThroughWeakReference.Prop;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment