Enumerables in .NET, which include popular collections like List<T>, Dictionary<TKey, TValue>, Hashset<T> and others, are fundamental building blocks in many applications. They allow developers to store, retrieve, and manipulate data efficiently. However, as with all things in software development, there’s always room for optimization. One such optimization is the setting of the initial capacity of these collections.
What is Initial Capacity?
Initial capacity refers to the number of elements that a collection is initialized to hold before any reallocation is needed. When items are added to a collection, and it exceeds its current capacity, the collection needs to allocate more memory, which can be a performance hit, especially if done frequently.
Setting Initial Capacity in Different Enumerables
Different collections in .NET provide ways to set their initial capacity:
List<T>: TheList<T>class has a constructor that accepts an integer representing the initial capacity. Additionally, there’s aCapacityproperty that can be set or queried to determine the number of elements the list can hold before resizing. By default, if you create aList<T>without specifying an initial capacity, it will be initialized with a capacity of 0. However, as soon as you add the first item, the capacity is increased to 4. This means that until you add a fifth item, no reallocation will occur.Dictionary<TKey, TValue>: The dictionary class also has a constructor that accepts an initial capacity. Dictionaries work a little bit differently and I have created a separate blog post here: https://codingbolt.net/2023/09/25/how-dictionary-works-in-net/- Other collections: Many other collections in .NET, like
HashSet<T>, also allow setting initial capacity through their constructors.
Performance Implications
Setting an appropriate initial capacity can lead to significant performance improvements, especially in scenarios where the size of the collection can be anticipated. By reducing the number of reallocations, applications can run faster and use memory more efficiently.
However, it’s worth noting that setting a very high initial capacity can also be counterproductive, as it might lead to wasted memory if the collection doesn’t grow to use all the allocated space.
Best Practices
- Always set the initial capacity when the expected number of elements is known.
- Use profilers and performance tools to monitor and adjust capacity settings as needed.
- Be cautious about setting extremely high capacities without a clear need.
Let’s see an example:
[Params(10)]
public int Number { get; set; }
[Benchmark]
public List<int> GetFixedList()
{
var list = new List<int>(Number);
for (int i = 0; i < Number; i++)
{
list.Add(i);
}
return list;
}
[Benchmark]
public List<int> GetList()
{
var list = new List<int>();
for (int i = 0; i < Number; i++)
{
list.Add(i);
}
return list;
}
Using BenchmarkDotNet we get the following results:
| Method | Number | Mean | Error | StdDev | Median | Allocated |
| GetFixedList | 10 | 25.918 ns | 0.0884 ns | 0.0784 ns | 25.902 ns | 96 B |
| GetList | 10 | 49.953 ns | 0.4559 ns | 0.4042 ns | 49.850 ns | 216 B |
Conclusion:
Understanding and setting the initial capacity of Enumerables in .NET is a simple yet effective way to optimize the performance of applications. As developers, it’s our responsibility to ensure that our applications run as efficiently as possible, and paying attention to details like initial capacity is a step in the right direction.
Leave a comment