0

C#: Dictionary Vs ConcurrentDictionary

C#: Dictionary Vs ConcurrentDictionary

In this article we are going to benchmark a C# Dictionary Vs ConcurrentDictionary while adding/updating key-value pairs.

This test came about while I was upgrading multiple code blocks from using C# Dictionary objects to ConcurrentDictionary objects. While doing so, I noticed that the application seemed to run slower despite now being multi-threaded.

I couldn’t tell if my eyes were fooling me, or something else was going on, so decided to write some test code to see how much, if at all, changing over to using ConcurrentDictionary objects affected performance.

The code is written in Visual Studio 2015 targeting .Net Framework version 4.5 x64. The source code is available at the end of this blog so you can benchmark it on your own system.

 

Notes about the code:

  • The “DoStuff1()” method is just a bunch of mathematical calculations to replicate heavy handed calculations the real code was doing. I added this in my test code to see if it affected the overall results even though in the test code it returns nothing – I liked it better than just doing a Thread.Sleep.
  • For Dictionary objects, I used the ContainsKey and Add methods with array notation d[k] = v for updating along with lock for thread-safety when running in Parallel.For loops
  • For ConcurrentDictionary objects, I used the inbuilt AddOrUpdate method.

 

The Benchmarks!

Let’s see what happened on my machine.

All times are indicated in seconds.milliseconds format.

Lower numbers indicate faster performance. Winning times marked in green.

Serial

Do Stuff?

1,000

100,000

10,000,000

Dictionary<int,string>

N

00.0000687

00.0137568

00.5258261

ConcurrentDictionary<int,string>

N

00.0078668

00.0308310

05.1309234


Dictionary<string,int>

N

00.0001477

00.0173439

03.1067780

ConcurrentDictionary<string,int>

N

00.0184019

00.0538895

16.7383171

 

Serial

Do Stuff?

1,000

100,000

10,000,000

Dictionary<int,string>

Y

00.0005953

00.0338717

02.4231792

ConcurrentDictionary<int,string>

Y

00.0004088

00.0623458

07.1167138

 

Dictionary<string,int>

Y

00.0003674

00.0391652

04.4576033

ConcurrentDictionary<string,int>

Y

00.0006012

00.0721350

16.8413835

 

Parallel

Do Stuff?

1,000

100,000

10,000,000

Dictionary<int,string>

N

00.0145580

00.0392367

00.9802224

ConcurrentDictionary<int,string>

N

00.0012365

00.0517408

05.8308073

 

Dictionary<string,int>

N

00.0013542

00.0239968

03.2670845

ConcurrentDictionary<string,int>

N

00.0013159

00.0725301

14.3347261

 

Parallel

Do Stuff?

1,000

100,000

10,000,000

Dictionary<int,string>

Y

00.0005621

00.0689331

02.8803307

ConcurrentDictionary<int,string>

Y

00.0014502

00.2194499

09.4257171

 

Dictionary<string,int>

Y

00.0007383

00.1126502

06.4293967

ConcurrentDictionary<string,int>

Y

00.0006692

00.2526517

17.4168156

 

Surmising the Results

Unless someone spots a flaw in my code, when there’s a need for speed, it’s best to use a Dictionary with a lock object instead of a ConcurrentDictionary when the number of items approaches 100,000 or more!

I never would have guessed that the amount of overhead from using a ConcurrentDictionary with its thead-safe methods would be significantly slower when starting to approach hundreds of thousands of objects. After seeing the results, it doesn’t surprise me though.

 

Should you change your code?

Dictionary Vs ConcurrentDictionary – should you change your code?

 

When your dictionaries will contain several thousand items and both speed and micro-optimizing every nano-second count, then you should consider implementing a normal Dictionary object and synchronize threads with a lock instead of using ConcurrentDictionary objects. You might consider putting in a few comments saying you know you can use a ConcurrentDictionary, but read this blog and saw it’s faster to implement with a single-threaded Dictionary so people understand why. πŸ™‚

 

Otherwise, using a ConcurrentDictionary and its associated methods will be more advantageous to use because:

  • It is in the framework – is more tested than custom locking routines, and you are not the one who has to maintain the code
  • It is scalable: if you are using or going to switch to multithreading, your code is already prepared for it

 

The Code:

 

David Lozinski