Revisiting Generic Collections
Earlier I asked if generic collections were faster than non-generic collections? I cooked up a quick test to confirm my suspicions that non-generics were slower because of the work it needs to box primitives and then add the reference to the collection- whereas for generic collections, it just needed to add the primitive to its own memory allocation.
But then a colleague asked- how about performance for collections of objects? Wouldn't a generic collection do the same as a non-generic collection? Both collections only contain references to objects on the heap- and there should be no difference, right? I updated my old test to check this assertion.
using System;
using System.Collections.Generic;
using System.Collections;
namespace TestGenericCollection
{
class TestGenericListVsArrayList
{
const int _numItems = 10000000;
static void TimeArrayListOfObjects()
{
Console.Out.WriteLine("TimeArrayListOfObjects start");
DateTime dtInsertStart = DateTime.Now;
ArrayList stringList = new ArrayList();
for (int i = 0; i < _numItems; i++)
{
stringList.Add(Convert.ToString(i));
}
DateTime dtReadStart = DateTime.Now;
for (int j = 0; j < _numItems; j++)
{
string retrievedInt = (string)stringList[j];
if (j % _numItems/10 == 0)
Console.Out.Write(".");
}
ShowStats(dtInsertStart, dtReadStart, "ArrayListOfObjects");
}
static void TimeArrayListOfPrimitives()
{
Console.Out.WriteLine("TimeArrayListOfPrimitives start");
DateTime dtInsertStart = DateTime.Now;
ArrayList intList = new ArrayList();
for (int i = 0; i < _numItems; i++)
{
intList.Add(i);
}
DateTime dtReadStart = DateTime.Now;
for (int j = 0; j < _numItems; j++)
{
int retrievedInt = (int)intList[j];
if (j % _numItems/10 == 0)
Console.Out.Write(".");
}
ShowStats(dtInsertStart, dtReadStart, "ArrayListOfPrimitives");
}
static void TimeGenericListOfObjects()
{
Console.Out.WriteLine("TimeGenericListOfObjects start");
DateTime dtInsertStart = DateTime.Now;
List<string> intList = new List<string>();
for (int i = 0; i < _numItems; i++)
{
intList.Add(Convert.ToString(i));
}
DateTime dtReadStart = DateTime.Now;
for (int j = 0; j < _numItems; j++)
{
string retrievedInt = intList[j];
if (j % _numItems/10 == 0)
Console.Out.Write(".");
}
ShowStats(dtInsertStart, dtReadStart, "GenericListOfObjects");
}
static void TimeGenericListOfPrimitives()
{
Console.Out.WriteLine("TimeGenericListOfPrimitives start");
DateTime dtInsertStart = DateTime.Now;
List<int> intList = new List<int>();
for (int i = 0; i < _numItems; i++)
{
intList.Add(i);
}
DateTime dtReadStart = DateTime.Now;
for (int j = 0; j < _numItems; j++)
{
int retrievedInt = intList[j];
if (j % _numItems / 10 == 0)
Console.Out.Write(".");
}
ShowStats(dtInsertStart, dtReadStart, "GenericListOfPrimitives");
}
/// <summary>
/// ShowStats show time procedure took to insert 1M items and to read 1M items
/// </summary>
/// <param name="dtInsertStart">Time when insert started</param>
/// <param name="dtReadStart">Time when read started</param>
/// <param name="msg">Name of procedure</param>
static void ShowStats(DateTime dtInsertStart, DateTime dtReadStart, string msg)
{
DateTime dtTimeNow = DateTime.Now;
TimeSpan wholeTime = dtTimeNow.Subtract(dtInsertStart);
TimeSpan insertTime = dtReadStart.Subtract(dtInsertStart);
TimeSpan readTime = dtTimeNow.Subtract(dtReadStart);
Console.Out.WriteLine("{3} took {0} ms in total, {1} ms to insert, {2} to read\n",
wholeTime.TotalMilliseconds,
insertTime.TotalMilliseconds,
readTime.TotalMilliseconds,
msg);
}
static void Main(string[] args)
{
switch(args[0])
{
case "1":
TimeGenericListOfPrimitives();
break;
case "2":
TimeGenericListOfObjects();
break;
case "3":
TimeArrayListOfPrimitives();
break;
case "4":
TimeArrayListOfObjects();
break;
}
}
}
}
The result?
>TestGenerics.exe 1
TimeGenericListOfPrimitives start
..........
GenericListOfPrimitives took 358.8006 ms in total, 202.8004 ms to insert, 156.0002 to read
>TestGenerics.exe 2
TimeGenericListOfObjects start
..........
GenericListOfObjects took 4336.8077 ms in total, 4180.8074 ms to insert, 156.0003 to read
>TestGenerics.exe 3
TimeArrayListOfPrimitives start
..........
ArrayListOfPrimitives took 2324.4041 ms in total, 2168.4038 ms to insert, 156.0003 to read
TestGenerics.exe 4
TimeArrayListOfObjects start
..........
ArrayListOfObjects took 4508.408 ms in total, 4352.4077 ms to insert, 156.0003 to read
It's fairly conclusive.
For collections of primitives (ints, longs, etc)- generic collections are definitely much faster than non-generic collections- because there is no need to box items.
For collections of objects- generic collections show no substantial improvement over non-generic collections.