Skip to main content

Benchmark of Querying Special Dictionary Containers

Haoyu JiaAbout 393 wordsAbout 1 minCSharpFundamentalsContainers

Result

Querying 1,000,000 times with 1,000,000 elements:

ContainerMean(ms)Min(ms)Max(ms)RangeAllocatedBytesOperationsRatio of Mean(ms)
Dictionary34.7230.6536.3316%1706561.00x
ConcurrentDictionary129.94128.51131.432%542603.74x
ConditionalWeakTable168.45150.94184.1720%5422364.85x

Querying 1,000,000 times with 1,000 elements:

ContainerMean(ms)Min(ms)Max(ms)RangeAllocatedBytesOperationsRatio of Mean(ms)
Dictionary11.9911.9512.031%869601.00x
ConcurrentDictionary10.149.9510.223%939600.84x
ConditionalWeakTable10.9810.8711.082%939600.91x

Conclusion

For queries with a large number of items within dictionaries, special dictionaries (ConcurrentDictionary<TKey, TValue>, ConditionalWeakTable<TKey, TValue>) are significantly slower than Dictionary<TKey, TValue>.

However, if there are not too many items (~ 1,000 in this benchmark) in the dictionaries, their query performance is almost identical.

Code

This is the code for benchmarking:

#load "BenchmarkDotNet"

void Main()
{
	RunBenchmark();
}

public static int Count = 1_000;
public static int Queries = 1_000_000;

public class Sample
{
	public string Name;
}

public static readonly Dictionary<Sample, string> DictionaryData = new();

public static readonly ConcurrentDictionary<Sample, string> ConcurrentDictionaryData = new();

public static readonly ConditionalWeakTable<Sample, string> ConditionalWeakTableData = new();

public static HashSet<Sample> Keys = new();

[GlobalSetup]
public void Setup()
{
	for (var index = 0; index < Count; ++index)
	{
		var text = RandomString();
		var target = new Sample()
		{
			Name = text
		};
		Keys.Add(target);
		DictionaryData[target] = text;
		ConcurrentDictionaryData[target] = text;
		ConditionalWeakTableData.AddOrUpdate(target, text);
	}
	Console.WriteLine($"Keys Count: {Keys.Count}");
}

private static Random random = new Random();

public static string RandomString()
{
	var length = random.Next(5, 30);
	const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
	return new string(Enumerable.Range(0, length)
		.Select(_ => chars[random.Next(chars.Length)]).ToArray());
}

[Benchmark]
public void Query_Dictionary()   // Benchmark methods must be public.
{
	var error = new HashSet<Sample>();
	
	for (int queries = 0; queries < Queries; queries += Keys.Count)
	{
		foreach (var key in Keys)
		{
			if (!DictionaryData.TryGetValue(key, out var text) || text != key.Name)
				error.Add(key);
		}
	}
}

[Benchmark]
public void Query_ConcurrentDictionary()
{
	var error = new HashSet<Sample>();
	for (int queries = 0; queries < Queries; queries += Keys.Count)
	{
		foreach (var key in Keys)
		{
			if (!ConcurrentDictionaryData.TryGetValue(key, out var text) || text != key.Name)
				error.Add(key);
		}
	}
}

[Benchmark]
public void Query_ConditionalWeakTable()
{
	var error = new HashSet<Sample>();
	for (int queries = 0; queries < Queries; queries += Keys.Count)
	{
		foreach (var key in Keys)
		{
			if (!ConditionalWeakTableData.TryGetValue(key, out var text) || text != key.Name)
				error.Add(key);
		}
	}
}
Last update: