Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save bdrupieski/603acb37b8751483e48e9e43c437f7cd to your computer and use it in GitHub Desktop.
Save bdrupieski/603acb37b8751483e48e9e43c437f7cd to your computer and use it in GitHub Desktop.
Performance and correctness comparison between foreach and Parallel.ForEach for adding items to a generic Dictionary<TKey, TValue>
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
namespace App
{
/// <summary>
/// This snippet shows a performance and correctness comparison between
/// foreach and <see cref="Parallel.ForEach"/> when adding a small number
/// of objects to a <see cref="Dictionary{TKey,TValue}"/>.
///
/// For a few items using a simple foreach loop is much faster and
/// using Parallel.ForEach is much slower. Adding items to
/// <see cref="Dictionary{TKey,TValue}"/> is also not thread-safe,
/// so using <see cref="Parallel.ForEach"/> sometimes results in
/// not adding all of the items you intended to add.
///
/// It can also throw an exception in <see cref="Dictionary{TKey,TValue}.Add"/>
/// and get stuck in an infinite loop I think in
/// <see cref="Dictionary{TKey,TValue}"/>'s private FindEntry method.
///
/// Note that you'll have to try to run this multiple times for it to actually
/// complete because <see cref="ParallelForEach"/> will sometimes enter
/// an infinite loop.
/// </summary>
public class CompareForEachToParallelForEachForAddingToDictionary
{
private const int Runs = 100000;
private static readonly Dictionary<string, object> ActionArguments = new Dictionary<string, object>
{
{"enrollmentId", 17L},
{"userid", "domain//username"},
{"data", null}
};
public static void DoIt()
{
var forEachMilliseconds = ForEach();
var parallelForEachMilliseconds = ParallelForEach();
var forEachTimesFasterThanParallel = (double)parallelForEachMilliseconds / forEachMilliseconds;
Console.WriteLine($"{nameof(ForEach)} is {forEachTimesFasterThanParallel:0.#}x faster than {nameof(ParallelForEach)}");
Console.WriteLine();
Console.WriteLine("Bonus:");
var linqMilliseconds = LinqToDictionary();
var forEachTimesFasterThanLinq = (double)linqMilliseconds / forEachMilliseconds;
Console.WriteLine($"{nameof(ForEach)} is {forEachTimesFasterThanLinq:0.#}x faster than {nameof(LinqToDictionary)}");
}
private static long ParallelForEach()
{
int numTimesFailedToAddAllItems = 0;
int numTimesThrewAnException = 0;
var sw = Stopwatch.StartNew();
for (int i = 0; i < Runs; i++)
{
try
{
var arguments = new Dictionary<string, string>();
Parallel.ForEach(ActionArguments, item =>
{
string value = item.Value?.ToString() ?? "<null>";
arguments.Add(item.Key, value);
});
if (arguments.Count != ActionArguments.Count)
{
numTimesFailedToAddAllItems++;
}
}
catch (Exception)
{
numTimesThrewAnException++;
}
}
sw.Stop();
Console.WriteLine($"{sw.ElapsedMilliseconds} ms using {nameof(ParallelForEach)}.");
Console.WriteLine($"{nameof(ParallelForEach)} failed to add all items {numTimesFailedToAddAllItems} times.");
Console.WriteLine($"{nameof(ParallelForEach)} threw an exception {numTimesThrewAnException} times.");
Console.WriteLine();
return sw.ElapsedMilliseconds;
}
private static long ForEach()
{
int numTimesFailedToAddAllItems = 0;
var sw = Stopwatch.StartNew();
for (int i = 0; i < Runs; i++)
{
var arguments = new Dictionary<string, string>();
foreach (var actionArgument in ActionArguments)
{
string value = actionArgument.Value?.ToString() ?? "<null>";
arguments.Add(actionArgument.Key, value);
}
if (arguments.Count != ActionArguments.Count)
{
numTimesFailedToAddAllItems++;
}
}
sw.Stop();
Console.WriteLine($"{sw.ElapsedMilliseconds} ms using {nameof(ForEach)}.");
Console.WriteLine($"{nameof(ForEach)} failed to add all items {numTimesFailedToAddAllItems} times.");
Console.WriteLine();
return sw.ElapsedMilliseconds;
}
private static long LinqToDictionary()
{
int numTimesFailedToAddAllItems = 0;
var sw = Stopwatch.StartNew();
for (int i = 0; i < Runs; i++)
{
var arguments = ActionArguments.ToDictionary(x => x.Key, y => new KeyValuePair<string, string>(y.Key, y.Value?.ToString() ?? "<null>"));
if (arguments.Count != ActionArguments.Count)
{
numTimesFailedToAddAllItems++;
}
}
sw.Stop();
Console.WriteLine($"{sw.ElapsedMilliseconds} ms using {nameof(LinqToDictionary)}.");
Console.WriteLine($"{nameof(LinqToDictionary)} failed to add all items {numTimesFailedToAddAllItems} times.");
Console.WriteLine();
return sw.ElapsedMilliseconds;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment