Reduce ConcurrentDictionary.TryGetValue overhead by stephentoub · Pull Request #37081 · dotnet/runtime (original) (raw)
The first commit is purely style; I initially found myself fixing up things as I was making changes for perf, and wanted those cleanly separated.
The rest of the commits are primarily trying to make ConcurrentDictionary<>.TryGetValue
faster, in particular by porting the kinds of changes that have been made in the past to Dictionary<>
. The biggest impact comes when no comparer is specified, especially when the key is a value type.
Method | Toolchain | Size | Mean | Error | StdDev | Ratio |
---|---|---|---|---|---|---|
GetInts | master | 1000 | 8.055 us | 0.0475 us | 0.0444 us | 1.00 |
GetInts | pr | 1000 | 4.345 us | 0.0171 us | 0.0152 us | 0.54 |
GetTypes | master | 1000 | 16.616 us | 0.0647 us | 0.0606 us | 1.00 |
GetTypes | pr | 1000 | 13.887 us | 0.0476 us | 0.0445 us | 0.84 |
GetStrings | master | 1000 | 37.802 us | 0.0953 us | 0.0892 us | 1.00 |
GetStrings | pr | 1000 | 35.077 us | 0.1693 us | 0.1501 us | 0.93 |
GetStringsCustom | master | 1000 | 43.375 us | 0.1143 us | 0.1069 us | 1.00 |
GetStringsCustom | pr | 1000 | 40.630 us | 0.1518 us | 0.1420 us | 0.94 |
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; using System; using System.Collections.Concurrent; using System.Linq;
[MemoryDiagnoser] public class Program { static void Main(string[] args) => BenchmarkSwitcher.FromAssemblies(new[] { typeof(Program).Assembly }).Run(args);
private ConcurrentDictionary<int, int> _int32Dictionary = new ConcurrentDictionary<int, int>();
private ConcurrentDictionary<string, string> _stringDictionary = new ConcurrentDictionary<string, string>();
private ConcurrentDictionary<Type, string> _typeDictionary = new ConcurrentDictionary<Type, string>();
private ConcurrentDictionary<string, string> _stringCustomDictionary = new ConcurrentDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
private string[] _stringKeys;
private Type[] _typeKeys;
[GlobalSetup]
public void Setup()
{
Type[] types = typeof(object).Assembly.GetTypes();
for (int i = 0; i < Size; i++)
{
_int32Dictionary.TryAdd(i, i);
string str = Guid.NewGuid().ToString("N");
_stringDictionary.TryAdd(str, str);
_stringCustomDictionary.TryAdd(str, str);
_typeDictionary.TryAdd(types[i], str);
}
_stringKeys = _stringDictionary.Keys.ToArray();
_typeKeys = _typeDictionary.Keys.ToArray();
}
[Params(1_000)]
public int Size { get; set; }
[Benchmark]
public int GetInts()
{
ConcurrentDictionary<int, int> d = _int32Dictionary;
int count = 0;
for (int i = 0; i < Size; i++)
{
if (d.TryGetValue(i, out _))
count++;
}
return count;
}
[Benchmark]
public int GetTypes()
{
ConcurrentDictionary<Type, string> d = _typeDictionary;
int count = 0;
foreach (Type key in _typeKeys)
{
if (d.TryGetValue(key, out _))
count++;
}
return count;
}
[Benchmark]
public int GetStrings()
{
ConcurrentDictionary<string, string> d = _stringDictionary;
int count = 0;
foreach (string key in _stringKeys)
{
if (d.TryGetValue(key, out _))
count++;
}
return count;
}
[Benchmark]
public int GetStringsCustom()
{
ConcurrentDictionary<string, string> d = _stringCustomDictionary;
int count = 0;
foreach (string key in _stringKeys)
{
if (d.TryGetValue(key, out _))
count++;
}
return count;
}
}
cc: @eiriktsarpalis, @benaadams, @AntonLapounov, @GrabYourPitchforks