Menu Close

Simple Tips to improve C# ConcurrentDictionary performance

Prefer Dictionary<>

the ConcurrentDictionary class uses more memory than the Dictionary class, and this can be a consideration when deciding which class to use in your application. The ConcurrentDictionary class is designed for use in concurrent scenarios where multiple threads may access the dictionary simultaneously, and it uses additional memory to support this concurrent access.

As a general rule, you should limit the number of ConcurrentDictionary instances that you use in your application to avoid using excessive memory. If you do not have any thread-safe requirements and do not need to support concurrent access to the dictionary, you should use the Dictionary class instead. This class uses less memory and can be a more efficient choice in these scenarios.

Use GetOrAdd

Avoid unnecessary operations on the dictionary. For example, if you are adding items to the dictionary but do not need to check whether they already exist, you can use the TryAdd method instead of the Add method. The TryAdd method does not check for the existence of the item, which can make it more efficient in scenarios where you are adding many items to the dictionary. You can also use the GetOrAdd method to avoid adding the same item multiple times, and the TryRemove method to avoid checking for the existence of an item before removing it.

if (!_concurrentDictionary .TryGetValue(cachedInstanceId, out _privateClass))
{
        _privateClass = new PrivateClass();
        _concurrentDictionary.TryAdd(cachedInstanceId, _privateClass);
}

Above code defeats the purpose of ConcurrentDictionary. The correct usage should be:

 _privateClass = _concurrentDictionary.GetOrAdd(new PrivateClass());

Set ConcurrencyLevel

the default constructor for the ConcurrentDictionary class uses a concurrency level of 4 * the number of cores on your machine, which may be overkill and can have a performance impact in some scenarios. In particular, in cloud computing environments where the number of cores can vary, using a fixed concurrency level of 4 * the number of cores can result in inefficient use of resources and may affect the performance of your application.

// Create a concurrent dictionary with a concurrency level of 2
var dictionary = new ConcurrentDictionary<string, int>(2);

Keys and Values are expensive

ConcurrentDictionary.Keys and .Values are expensive; not only because they take a lock but also these operations need to build a list object. A good practice is to avoid using them, when you can use enumeration on KeyValuePairs instead.

// Create a concurrent dictionary with some initial data
var dictionary = new ConcurrentDictionary<string, int>
{
    { "key1", 1 },
    { "key2", 2 },
    { "key3", 3 },
};

// Get the keys from the dictionary using the KeyValuePairs property and the ToArray method
string[] keys = dictionary.KeyValuePairs.ToArray(pair => pair.Key);

// Get the values from the dictionary using the KeyValuePairs property and the ToArray method
int[] values = dictionary.KeyValuePairs.ToArray(pair => pair.Value);

Use ContainsKey before lock operations

if (this._concurrentDictionary.TryRemove(itemKey, out value))
{
      // some operations
}

simply adding a non-thread safe containskey on top of this operation will greatly improve the performance

if (this._concurrentDictionary.ContainKey(itemKey))
{
       if (this._concurrentDictionary.TryRemove(itemKey, out value))
       {
             // some operations
       }
}

Avoid ConcurrentDictionary.Count

Count operation in ConcurrentDictionary is expensive. what you can do is to wrap the object and have a lock free count. You can use the Interlocked.Increment method in scenarios where you need to atomically increment a value in a thread-safe manner. For example, you could use it to increment a counter that tracks the number of active connections to a server or to increment a counter that tracks the number of items in a queue.

public class Counter
{
    private int count = 0;

    public void Increment()
    {
        // Increment the count using the Interlocked.Increment method
        Interlocked.Increment(ref this.count);
        //or
        // Interlocked.Decrement(ref this.count);
    }

    public int GetCount()
    {
        return this.count;
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *