I am using concurrent dictionary to implement a simple cache but when I am running with the debugger attached visual studio the concurrent dictionary with Lazy CacheConcurrentWithLazy becomes really slow versus the CacheWithoutLazy.
running without the debugger attached it takes around 8000 milliseconds for either cache running the following loop.
- lazy 9008 milliseconds
- non-lazy 8960 milliseconds
running with the debugger attached it takes more than 20000 milliseconds + to run the CacheConcurrentWithLazy one.
- lazy 19779 milliseconds
- non-lazy 9001 milliseconds
I have tried different debug configuration but can't find a way to fix this problem.
I use visual studio 2019, .Net framework 4.8
To reproduce the problem just new to create a new console app with .net framework 4.8 and copy the following code
Run with the debugger attached -> F5
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
namespace ConsoleApp3
{
internal class Program
{
private static void Main(string[] args)
{
var ts = new Stopwatch();
ts.Start();
var cacheConcurrentWithLazy = new CacheConcurrentWithLazy<int, double>(FunctionExample());
for (var i = 0; i < 100000; i++) Console.WriteLine(cacheConcurrentWithLazy[i]);
ts.Stop();
var ts2 = new Stopwatch();
ts2.Start();
var cache = new CacheWithoutLazy<int, double>(FunctionExample());
for (var i = 0; i < 100000; i++) Console.WriteLine(cache[i]);
ts2.Stop();
Console.WriteLine($"lazy {ts.ElapsedMilliseconds} ms");
Console.WriteLine($"non lazy {ts2.ElapsedMilliseconds} ms");
Console.ReadKey();
}
private static Func<int, double> FunctionExample()
{
Func<int, double> missing = x => 1.0D;
return missing;
}
}
public class CacheConcurrentWithLazy<TKey, TValue>
{
private readonly Func<TKey, TValue> _onMissing;
private readonly ConcurrentDictionary<TKey, Lazy<TValue>> _values;
private CacheConcurrentWithLazy(ConcurrentDictionary<TKey, Lazy<TValue>> dictionary,
Func<TKey, TValue> onMissing)
{
_onMissing = OnMissing;
_values = dictionary;
_onMissing = onMissing;
}
public CacheConcurrentWithLazy(Func<TKey, TValue> onMissing) : this(
new ConcurrentDictionary<TKey, Lazy<TValue>>(), onMissing)
{
}
public TValue this[TKey key]
{
get
{
return _values.GetOrAdd(key,
new Lazy<TValue>(() => _onMissing(key), LazyThreadSafetyMode.ExecutionAndPublication)).Value;
}
}
private TValue OnMissing(TKey key)
{
throw new KeyNotFoundException($"Key '{key}' could not be found");
}
}
public class CacheWithoutLazy<TKey, TValue>
{
private readonly Func<TKey, TValue> _onMissing;
private readonly ConcurrentDictionary<TKey, TValue> _values;
private CacheWithoutLazy(ConcurrentDictionary<TKey, TValue> dictionary, Func<TKey, TValue> onMissing)
{
_onMissing = OnMissing;
_values = dictionary;
_onMissing = onMissing;
}
public CacheWithoutLazy(Func<TKey, TValue> onMissing) : this(new ConcurrentDictionary<TKey, TValue>(), onMissing)
{
}
public TValue this[TKey key]
{
get
{
return _values.GetOrAdd(key, _onMissing(key));
}
}
private TValue OnMissing(TKey key)
{
throw new KeyNotFoundException($"Key '{key}' could not be found");
}
}
}