You may already know that there are 2 types of dictionary classes available in .NET framework - Dictionary and ConcurrentDictionary.
ConcurrentDictionary is possibly the right choice in cases when you have to maintain a static dictionary in an application where parallel access to the dictionary is possible. Surely you can do the same with a Dictionary instance but then you have to write lock statements around dictionary operation statements.
How about comparing the performance overhead between the two approaches. I wrote a simple utility that performs random read/write/delete operations on the two types and measure any difference. To my surprise, I did not see any big difference. Both approaches showed similar numbers :)
private static bool shouldStop = false;
private static object lockObj = new object();
private static Dictionary dict = new Dictionary();
private static ConcurrentDictionary dict2 = new ConcurrentDictionary();
private static Random r = new Random();
private static string[] OperationsList = new string[11]
{
"R", "W", "R", "W", "D", "R", "R", "R", "R", "W", "D"
};
static void Main(string[] args)
{
dict.Add("A", "B");
dict2.GetOrAdd("A", "B");
Console.WriteLine("Starting...");
Task t = new Task(RunDictionaryTest);
t.Start();
Console.ReadKey();
shouldStop = true;
Console.WriteLine("Stopping...");
}
private static void RunDictionaryTest()
{
Action a = () =>
{
Console.WriteLine("Running...");
while (!shouldStop)
{
var number = r.Next(0, 10);
////DoRandomDictionaryTryGetOperation(OperationsList[number]);
DoRandomDictionaryOperationWithContainsKey(OperationsList[number]);
////DoRandomConcurrentDictionaryOperation(OperationsList[number]);
////Thread.Sleep(10);
}
};
Task[] tasks = new Task[100];
for (int i = 0; i < 100; i++)
{
tasks[i] = new Task(a);
}
for (int i = 0; i < 100; i++)
{
tasks[i].Start();
}
Task.WaitAll(tasks);
}
private static void DoRandomConcurrentDictionaryOperation(string operationName)
{
string val;
Stopwatch sw = new Stopwatch();
sw.Start();
switch (operationName)
{
case "R":
if (dict2.TryGetValue("Key", out val))
{
Console.WriteLine("Read:" + val);
}
else
{
Console.WriteLine("No Read");
}
break;
case "W":
val = Guid.NewGuid().ToString();
string val2 = dict2.AddOrUpdate("Key", val, (k, v) => val);
if (!val.Equals(val2))
{
Console.WriteLine("no write");
}
break;
case "D":
if(!dict2.TryRemove("Key", out val))
{
Console.WriteLine("no delete");
}
break;
}
sw.Stop();
Console.WriteLine("Time taken" + sw.ElapsedMilliseconds.ToString());
}
private static void DoRandomDictionaryTryGetOperation(string operationName)
{
string val;
Stopwatch sw = new Stopwatch();
sw.Start();
switch (operationName)
{
case "R":
lock (lockObj)
{
if (dict.TryGetValue("Key", out val))
{
Console.WriteLine("Read:" + val);
}
else
{
Console.WriteLine("No Read");
}
}
break;
case "W":
lock (lockObj)
{
if (dict.TryGetValue("Key", out val))
{
Console.WriteLine("no write");
}
else
{
dict.Add("Key", Guid.NewGuid().ToString());
}
}
break;
case "D":
lock (lockObj)
{
if (dict.TryGetValue("Key", out val))
{
dict.Remove("Key");
}
else
{
Console.WriteLine("no delete");
}
}
break;
}
sw.Stop();
Console.WriteLine("Time taken" + sw.ElapsedMilliseconds.ToString());
}
private static void DoRandomDictionaryOperationWithContainsKey(string operationName)
{
Stopwatch sw = new Stopwatch();
sw.Start();
switch (operationName)
{
case "R":
lock (lockObj)
{
if (dict.ContainsKey("Key"))
{
Console.WriteLine("Read:" + dict["Key"]);
}
else
{
Console.WriteLine("No Read");
}
}
break;
case "W":
lock (lockObj)
{
if (dict.ContainsKey("Key"))
{
Console.WriteLine("no write");
}
else
{
dict.Add("Key", Guid.NewGuid().ToString());
}
}
break;
case "D":
lock (lockObj)
{
if (dict.ContainsKey("Key"))
{
dict.Remove("Key");
}
else
{
Console.WriteLine("no delete");
}
}
break;
}
sw.Stop();
Console.WriteLine("Time taken" + sw.ElapsedMilliseconds.ToString());
}
ConcurrentDictionary is possibly the right choice in cases when you have to maintain a static dictionary in an application where parallel access to the dictionary is possible. Surely you can do the same with a Dictionary instance but then you have to write lock statements around dictionary operation statements.
How about comparing the performance overhead between the two approaches. I wrote a simple utility that performs random read/write/delete operations on the two types and measure any difference. To my surprise, I did not see any big difference. Both approaches showed similar numbers :)
private static bool shouldStop = false;
private static object lockObj = new object();
private static Dictionary
private static ConcurrentDictionary
private static Random r = new Random();
private static string[] OperationsList = new string[11]
{
"R", "W", "R", "W", "D", "R", "R", "R", "R", "W", "D"
};
static void Main(string[] args)
{
dict.Add("A", "B");
dict2.GetOrAdd("A", "B");
Console.WriteLine("Starting...");
Task t = new Task(RunDictionaryTest);
t.Start();
Console.ReadKey();
shouldStop = true;
Console.WriteLine("Stopping...");
}
private static void RunDictionaryTest()
{
Action a = () =>
{
Console.WriteLine("Running...");
while (!shouldStop)
{
var number = r.Next(0, 10);
////DoRandomDictionaryTryGetOperation(OperationsList[number]);
DoRandomDictionaryOperationWithContainsKey(OperationsList[number]);
////DoRandomConcurrentDictionaryOperation(OperationsList[number]);
////Thread.Sleep(10);
}
};
Task[] tasks = new Task[100];
for (int i = 0; i < 100; i++)
{
tasks[i] = new Task(a);
}
for (int i = 0; i < 100; i++)
{
tasks[i].Start();
}
Task.WaitAll(tasks);
}
private static void DoRandomConcurrentDictionaryOperation(string operationName)
{
string val;
Stopwatch sw = new Stopwatch();
sw.Start();
switch (operationName)
{
case "R":
if (dict2.TryGetValue("Key", out val))
{
Console.WriteLine("Read:" + val);
}
else
{
Console.WriteLine("No Read");
}
break;
case "W":
val = Guid.NewGuid().ToString();
string val2 = dict2.AddOrUpdate("Key", val, (k, v) => val);
if (!val.Equals(val2))
{
Console.WriteLine("no write");
}
break;
case "D":
if(!dict2.TryRemove("Key", out val))
{
Console.WriteLine("no delete");
}
break;
}
sw.Stop();
Console.WriteLine("Time taken" + sw.ElapsedMilliseconds.ToString());
}
private static void DoRandomDictionaryTryGetOperation(string operationName)
{
string val;
Stopwatch sw = new Stopwatch();
sw.Start();
switch (operationName)
{
case "R":
lock (lockObj)
{
if (dict.TryGetValue("Key", out val))
{
Console.WriteLine("Read:" + val);
}
else
{
Console.WriteLine("No Read");
}
}
break;
case "W":
lock (lockObj)
{
if (dict.TryGetValue("Key", out val))
{
Console.WriteLine("no write");
}
else
{
dict.Add("Key", Guid.NewGuid().ToString());
}
}
break;
case "D":
lock (lockObj)
{
if (dict.TryGetValue("Key", out val))
{
dict.Remove("Key");
}
else
{
Console.WriteLine("no delete");
}
}
break;
}
sw.Stop();
Console.WriteLine("Time taken" + sw.ElapsedMilliseconds.ToString());
}
private static void DoRandomDictionaryOperationWithContainsKey(string operationName)
{
Stopwatch sw = new Stopwatch();
sw.Start();
switch (operationName)
{
case "R":
lock (lockObj)
{
if (dict.ContainsKey("Key"))
{
Console.WriteLine("Read:" + dict["Key"]);
}
else
{
Console.WriteLine("No Read");
}
}
break;
case "W":
lock (lockObj)
{
if (dict.ContainsKey("Key"))
{
Console.WriteLine("no write");
}
else
{
dict.Add("Key", Guid.NewGuid().ToString());
}
}
break;
case "D":
lock (lockObj)
{
if (dict.ContainsKey("Key"))
{
dict.Remove("Key");
}
else
{
Console.WriteLine("no delete");
}
}
break;
}
sw.Stop();
Console.WriteLine("Time taken" + sw.ElapsedMilliseconds.ToString());
}