In C#, all types inherit from a single root, the object type. This allows for primitive types such as int
, boolean
, and char
to be addressed as objects throughout the system. There is some debate as to whether or not this is a good thing as there is some overhead involved in packaging up or “boxing” primitive types. Other languages, such as Java, don’t require boxing primitive types into reference types, thus trading overhead for convenience. However, in the .NET Framework, boxing primitive types is standard practice.
I was curious as to how much overhead is involved in boxing and unboxing primitive, value types in and out of the Object
reference type, so I wrote this small test program.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
namespace BoxingAndGenerics
{
class Program
{
static void Main()
{
const int numberOfInterations = 8000000;
var startTime = Stopwatch.GetTimestamp();
ArrayList aListOfStrings = new ArrayList();
for (var i = 0; i< numberOfInterations; i++)
{
string theString = i.ToString();
aListOfStrings.Add(theString);
string unpackedString = (string)aListOfStrings[i];
}
var ellapsedTime = (Stopwatch.GetTimestamp() - startTime) / (double) Stopwatch.Frequency;
Console.WriteLine("Time to create string array with boxing: {0}", ellapsedTime);
startTime = Stopwatch.GetTimestamp();
List<string> aGenericListOfStrings = new List<string>();
for (var i = 0; i < numberOfInterations; i++)
{
string theString = i.ToString();
aGenericListOfStrings.Add(theString);
string unpackedString = (string)aGenericListOfStrings[i];
}
ellapsedTime = (Stopwatch.GetTimestamp() - startTime) / (double)Stopwatch.Frequency;
Console.WriteLine("Time to create string array using Generic array: {0}", ellapsedTime);
startTime = Stopwatch.GetTimestamp();
ArrayList aListOfInts = new ArrayList();
for (var i = 0; i < numberOfInterations; i++)
{
aListOfInts.Add(i);
int unpackedInt = (int)aListOfInts[i];
}
ellapsedTime = (Stopwatch.GetTimestamp() - startTime) / (double)Stopwatch.Frequency;
Console.WriteLine("Time to create int array with boxing: {0}", ellapsedTime);
startTime = Stopwatch.GetTimestamp();
List<int> aGenericListOfInts = new List<int>();
for (var i = 0; i < numberOfInterations; i++)
{
aGenericListOfInts.Add(i);
int unpackedInt = (int)aGenericListOfInts[i];
}
ellapsedTime = (Stopwatch.GetTimestamp() - startTime) / (double)Stopwatch.Frequency;
Console.WriteLine("Time to create int array using Generic array: {0}", ellapsedTime);
startTime = Stopwatch.GetTimestamp();
ArrayList aListOfDoubles = new ArrayList();
for (var i = 0; i < numberOfInterations; i++)
{
double theDouble = (double) i;
aListOfDoubles.Add(theDouble);
double unpackedDouble = (double)aListOfDoubles[i];
}
ellapsedTime = (Stopwatch.GetTimestamp() - startTime) / (double)Stopwatch.Frequency;
Console.WriteLine("Time to create double array with boxing: {0}", ellapsedTime);
startTime = Stopwatch.GetTimestamp();
List<double> aGenericListOfDoubles = new List<double>();
for (var i = 0; i < numberOfInterations; i++)
{
double theDouble = (double) i;
aGenericListOfDoubles.Add(theDouble);
double unpackedDouble = (double)aGenericListOfDoubles[i];
}
ellapsedTime = (Stopwatch.GetTimestamp() - startTime) / (double)Stopwatch.Frequency;
Console.WriteLine("Time to create double array using Generic array: {0}", ellapsedTime);
Console.ReadLine();
}
}
}
After running the program, this is the output:
As expected, string
s are always going to be expensive to deal with due to their immutable nature. Interestingly, you can see that numeric types are about 10 times slower when dealing with boxing. Therefore, when possible we should use generic collection types so that the CLR knows in advance the type that it is dealing with and can avoid the overhead of boxing.CodeProject