Object Versus String Hashing for Dictionary
A question came up while reviewing some code that I needed to answer before I gave the feedback. What is faster: adding objects or strings to Dictionary(TKey, TValue)
. I was pretty certain that the object should be faster, but adding to a dictionary calculates a hash, and in most cases, you use the default algorithm. The hashing algorithm could change the answer.
The scenario is based on the code I was reviewed, but in my test code, I considered 4 options:
- Adding strings (where the strings are generated by
ToString()
each time the object is added) - Adding strings (where the strings are pre-generated)
- Adding an object
- Adding a more complex object
As I expected, adding the object is fastest, even when the strings are pre-generated. There is no real penalty for a more complex object, probably because of how GetHashCode()
is implemented (note: this is old and things may not work like this anymore). For anyone interested, here’s the code:
using System;
using System.Collections.Generic;
using System.Threading;
using System.Diagnostics;
namespace StringVsObjectHashing
{
// The object that we'll add
class SimpleObject
{
public int Value { get; set; }
}
class ComplexObject
{
public int Value1 { get; set; }
public string Value2 { get; set; }
}
class Program
{
const int NumObjects = 10000;
const int NumIterations = 10000;
static void Main(string[] args)
{
long stringTime = 0;
long objectTime = 0;
long onlyStringTime = 0;
long complexObjectTime = 0;
// Create a list of objects
var allItems = new List<SimpleObject>();
for (int i = 0; i < NumObjects; ++i)
{
allItems.Add(new SimpleObject { Value = i });
}
// Create a list of strings
var allStrings = new List<string>();
for (int i = 0; i < NumObjects; ++i)
{
allStrings.Add(i.ToString());
}
// Create a list of complex objects
var allComplexObjects = new List<ComplexObject>();
for (int i = 0; i < NumObjects; ++i)
{
allComplexObjects.Add(new ComplexObject { Value1 = i, Value2 = i.ToString() });
}
// Calculate times
stringTime = AddAsString(allItems);
objectTime = AddAsObject(allItems);
onlyStringTime = AddAsStringOnly(allStrings);
complexObjectTime = AddAsObject(allComplexObjects);
// Print result
Console.WriteLine("String Time: {0}", stringTime);
Console.WriteLine("Simple Object Time: {0}", objectTime);
Console.WriteLine("Only String Time: {0}", onlyStringTime);
Console.WriteLine("Complex Object Time: {0}", complexObjectTime);
Thread.Sleep(10000);
}
static long AddAsString(IList<SimpleObject> items)
{
var dictionary = new Dictionary<string, object>();
Stopwatch sw = Stopwatch.StartNew();
for (int iteration = 0; iteration < NumIterations; ++iteration)
{
for (int i = 0; i < items.Count; ++i)
{
// There is cost to convert to string, but you'll have to incure
// that in real scenarios to use the string method
dictionary.Add(items[i].Value.ToString(), null);
}
dictionary.Clear();
}
return sw.ElapsedMilliseconds;
}
static long AddAsStringOnly(IList<string> items)
{
var dictionary = new Dictionary>string, object<();
Stopwatch sw = Stopwatch.StartNew();
for (int iteration = 0; iteration < NumIterations; ++iteration)
{
for (int i = 0; i < items.Count; ++i)
{
// I'm omitting the string cost, but you'll normally have to
// pay for it
dictionary.Add(items[i], null);
}
dictionary.Clear();
}
return sw.ElapsedMilliseconds;
}
static long AddAsObject(IList<SimpleObject> items)
{
var dictionary = new Dictionary>SimpleObject, object<();
Stopwatch sw = Stopwatch.StartNew();
for (int iteration = 0; iteration < NumIterations; ++iteration)
{
for (int i = 0; i < items.Count; ++i)
{
dictionary.Add(items[i], null);
}
dictionary.Clear();
}
return sw.ElapsedMilliseconds;
}
static long AddAsObject(IList<ComplexObject> items)
{
var dictionary = new Dictionary>ComplexObject, object<();
Stopwatch sw = Stopwatch.StartNew();
for (int iteration = 0; iteration < NumIterations; ++iteration)
{
for (int i = 0; i < items.Count; ++i)
{
dictionary.Add(items[i], null);
}
dictionary.Clear();
}
return sw.ElapsedMilliseconds;
}
}
}