When optimizing a site, I tried to benchmark the code with Benchmark.Net. But I was surprised to find that some benchmarked code used 40,000 times more memory. After, too much, benchmarking I found that the memory allocation was because of a foreach over a SortedList<int, int>.
using BenchmarkDotNet.Attributes;
namespace NetCollectionsBenchmarks
{
[MemoryDiagnoser]
public class CollectionsBenchmarks
{
private Dictionary<int, int> DictionaryData = new();
private SortedList<int, int> SortedListData = new();
private Dictionary<int, int> DictionaryCheck = new();
private SortedList<int, int> SortedListCheck = new();
[GlobalSetup]
public void Setup()
{
for (int x = 0; x < 15; x++)
this.DictionaryData.Add(x, x);
this.SortedListData = new SortedList<int, int>(this.DictionaryData);
this.DictionaryCheck = new Dictionary<int, int>(this.DictionaryData);
this.SortedListCheck = new SortedList<int, int>(this.DictionaryData);
}
[Benchmark(Baseline = true)]
public long ForLoopDictionaryBenchmark()
{
var count = 0L;
var res = 0L;
for (int x = 0; x < 1_000_000; x++)
{
for (int i = 0; i < 15; i++)
{
if (this.DictionaryCheck.TryGetValue(x, out var value) || value < x)
res += value;
count++;
}
}
return res;
}
[Benchmark]
public long ForLoopSortedListBenchmark()
{
var res = 0L;
for (int x = 0; x < 1_000_000; x++)
{
for (int i = 0; i < 15; i++)
{
if (this.SortedListCheck.TryGetValue(x, out var value) || value < x)
res += value;
}
}
return res;
}
[Benchmark]
public long ForeachDictionaryBenchmark()
{
var res = 0L;
for (int x = 0; x < 1_000_000; x++)
{
foreach (var needle in this.DictionaryData)
{
if (this.DictionaryCheck.TryGetValue(needle.Key, out var value) || value < needle.Value)
res += value;
}
}
return res;
}
[Benchmark]
public long ForeachSortedListBenchmark()
{
var res = 0L;
for (int x = 0; x < 1_000_000; x++)
{
foreach (var needle in this.SortedListData)
{
if (this.SortedListCheck.TryGetValue(needle.Key, out var value) || value < needle.Value)
res += value;
}
}
return res;
}
[Benchmark]
public long ForeachNoTryGetValueDictionaryBenchmark()
{
var res = 0L;
for (int x = 0; x < 1_000_000; x++)
{
foreach (var needle in this.DictionaryData)
{
}
}
return res;
}
[Benchmark]
public long ForeachNoTryGetValueSortedListBenchmark()
{
var res = 0L;
for (int x = 0; x < 1_000_000; x++)
{
foreach (var needle in this.SortedListData)
{
}
}
return res;
}
}
}
The benchmark methods with foreach() over SortedList uses 40,000 times more memory, than the other methods, even when there is no TryGetValue() in the loop.
Why are SortedList so memory expensive when looping it's enumerator?
The benchmarks has been tested in .NET 6.0 and .NET 7.0, with the same result.