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