Performance comparison of .NET data types on Equality checks.

The intention of this post is NOT to suggest any particular method for performing equality check but just to show some interesting data related to performance when equality check is done for some of the fundamental datatypes in .NET.

How the test is done

A collection of million items of  the data type is stored into a list. Iterating through the items in the collection, each item is checked for equality with another value. Time taken for checking the equality is noted. The chart shown below shows the results obtained. The value along the y axis denotes the time in ticks, where as on the x axis, the datatype used to perform the test. Lower the value in y axis, faster it is.

Conclusions

The following can be deduced

  • Equality check using == operator for System.Int32 and Double are considerably faster than .Equals() method.
  • Decimal, Guid, DateTime and Nullable(Of Integer) have almost same performance cost for .Equals() and == methods.
  • .Equals() method, compared to == operator does faster equality checks for Strings
   Dim stopWatch As New Stopwatch

        Dim nullableIntList As New List(Of Nullable(Of Integer))
        Dim intList As New List(Of Integer)

        Dim doubleList As New List(Of Double)
        Dim decimalList As New List(Of Decimal)

        Dim guidList As New List(Of Guid)
        Dim dateList As New List(Of DateTime)

        Dim stringList As New List(Of String)
        Dim startDate As New DateTime(1, 1, 1)

        For i As Integer = 0 To 1000000

            nullableIntList.Add(i)
            intList.Add(i)
            doubleList.Add(CDbl(i))
            decimalList.Add(CDec(i))

            guidList.Add(New Guid)

            dateList.Add(startDate)
            stringList.Add(startDate.ToLongDateString)
            startDate = startDate.AddDays(1)

        Next

        Dim checkResult As Boolean = False
        Dim intToCompare As Integer = intToCompare

        stopWatch.Start()

        For Each nullableInt In intList

        Next

        stopWatch.Stop()

        Debug.WriteLine("looping throught items took " & stopWatch.ElapsedTicks)
        stopWatch.Restart()

        For Each nullableInt In intList
            checkResult = (nullableInt = intToCompare)
        Next

        stopWatch.Stop()

        Debug.WriteLine("== for Nullable Int took (ticks)" & stopWatch.ElapsedTicks)
        Debug.WriteLine("== for Nullable  Int took (ms)" & stopWatch.ElapsedMilliseconds)

        stopWatch.Restart()
        For Each nullableInt In nullableIntList
            checkResult = (nullableInt.Equals(intToCompare))
        Next

        stopWatch.Stop()

        Debug.WriteLine(".Equals for Nullable Int took (ticks)" & stopWatch.ElapsedTicks)
        Debug.WriteLine(".Equals for Nullable Int took (ms)" & stopWatch.ElapsedMilliseconds)

        stopWatch.Restart()
        For Each intItem In intList
            checkResult = (intItem = intToCompare)
        Next

        stopWatch.Stop()

        Debug.WriteLine("== for Int took (ticks)" & stopWatch.ElapsedTicks)
        Debug.WriteLine("== for Int took (ms)" & stopWatch.ElapsedMilliseconds)

        stopWatch.Restart()
        For Each intItem In intList
            checkResult = (intItem.Equals(intToCompare))
        Next

        stopWatch.Stop()

        Debug.WriteLine(".Equals for Int took (ticks)" & stopWatch.ElapsedTicks)
        Debug.WriteLine(".Equals for Int took (ms)" & stopWatch.ElapsedMilliseconds)

        Dim doubleToCompare As Double = 3.4

        stopWatch.Restart()
        For Each dblItem In doubleList
            checkResult = (dblItem = doubleToCompare)
        Next

        stopWatch.Stop()

        Debug.WriteLine("== for Double took (ticks)" & stopWatch.ElapsedTicks)
        Debug.WriteLine("== for Double took (ms)" & stopWatch.ElapsedMilliseconds)

        stopWatch.Restart()
        For Each dblItem In doubleList
            checkResult = (dblItem.Equals(doubleToCompare))
        Next

        stopWatch.Stop()

        Debug.WriteLine(".Equals for Double took (ticks)" & stopWatch.ElapsedTicks)
        Debug.WriteLine(".Equals for Double took (ms)" & stopWatch.ElapsedMilliseconds)

        Dim decimalToCompare As Decimal = 3.4D

        stopWatch.Restart()
        For Each decItem In decimalList
            checkResult = (decItem = decimalToCompare)
        Next

        stopWatch.Stop()

        Debug.WriteLine("== for Decimal took (ticks)" & stopWatch.ElapsedTicks)
        Debug.WriteLine("== for Decimal took (ms)" & stopWatch.ElapsedMilliseconds)

        stopWatch.Restart()
        For Each decItem In decimalList
            checkResult = (decItem.Equals(decimalToCompare))
        Next

        stopWatch.Stop()

        Debug.WriteLine(".Equals for Decimal took (ticks)" & stopWatch.ElapsedTicks)
        Debug.WriteLine(".Equals for Decimal took (ms)" & stopWatch.ElapsedMilliseconds)

        Dim guidToCompare As Guid = New Guid

        stopWatch.Restart()
        For Each guidItem In guidList
            checkResult = (guidItem = guidToCompare)
        Next

        stopWatch.Stop()

        Debug.WriteLine("== for Guid took (ticks)" & stopWatch.ElapsedTicks)
        Debug.WriteLine("== for Guid took (ms)" & stopWatch.ElapsedMilliseconds)

        stopWatch.Restart()
        For Each guidItem In guidList
            checkResult = (guidItem.Equals(guidToCompare))
        Next

        stopWatch.Stop()

        Debug.WriteLine(".Equals for Guid took (ticks)" & stopWatch.ElapsedTicks)
        Debug.WriteLine(".Equals for Guid took (ms)" & stopWatch.ElapsedMilliseconds)

        Dim dateToCompare As DateTime = DateTime.Now.Date

        stopWatch.Restart()

        For Each dateItem In dateList
            checkResult = (dateToCompare = dateItem)
        Next

        stopWatch.Stop()

        Debug.WriteLine("== for DateTime took (ticks)" & stopWatch.ElapsedTicks)
        Debug.WriteLine("== for DateTime took (ms)" & stopWatch.ElapsedMilliseconds)

        stopWatch.Restart()

        For Each dateItem In dateList
            checkResult = (dateItem.Equals(dateItem))
        Next

        stopWatch.Stop()

        Debug.WriteLine(".Equals for DateTime took (ticks)" & stopWatch.ElapsedTicks)
        Debug.WriteLine(".Equals for DateTime took (ms)" & stopWatch.ElapsedMilliseconds)

        Dim stringToCompare As String = "Hello World!"

        stopWatch.Restart()

        For Each stringItem In stringList
            checkResult = (stringItem = stringToCompare)
        Next

        stopWatch.Stop()

        Debug.WriteLine("== for String took (ticks)" & stopWatch.ElapsedTicks)
        Debug.WriteLine("== for String took (ms)" & stopWatch.ElapsedMilliseconds)

        stopWatch.Restart()

        For Each stringItem In stringList
            checkResult = (stringItem.Equals(stringToCompare))
        Next

        stopWatch.Stop()

        Debug.WriteLine(".Equals for String took (ticks)" & stopWatch.ElapsedTicks)
        Debug.WriteLine(".Equals for String took (ms)" & stopWatch.ElapsedMilliseconds)

        Console.ReadKey()

When to use .Equals and == (or = in VB) operator.

There is a significant difference in how .Equals method and == operator methods performs. The difference is subject to what type of object it is, whether is a value type or a reference type. With an exception to String which is a reference type which overloads the == operator.

The .Equals method is a virtual method and it is implemented in the Object class. This function can be overridden by any class and where as == is an operator which can be overloaded by any class (Operator overloading). For the valuetypes the default implementation of System.Object methods including .Equals is overridden in System.Valuetype class (MSDN). And for value types “If none of the fields of the current instance and obj(parameter passed to the function) are reference types, the Equals method performs a byte-by-byte comparison of the two objects in memory. Otherwise, it uses reflection to compare the corresponding fields of obj and this instance.” (MSDN). This is a significantly slower especially when reflection is employed to find the equality.

Having said that some of the valuetypes does have .Equals methods which takes in the object to compare which is of the same type of the instance. For reference types .Equals checks content equality and where as == operator looks for referential equality(Is operator in VB.NET).

The overloaded operator == is a more readable way to check the equality especially for value types and for strings which overloads == operator. Hence my suggestion is for value types and for strings always use the operators and when you have to do content equality for reference types use the .Equals() method.

Multikey Dictionary – A multikey index lookup collection.

When querying a large collection which consists of millions of items, you may encounter some serious performance issues. If you are using an unordered collection, In worst case scenario it will take O(n) to fetch an item. Where as is in a sorted collection, it will be search better than that of an unordered collection – with a performance complexity O(log n). Where as with a dictionary it is a constant time O(1) and offers the best deal.

Unfortunately, a dictionary can only accept one key. What if you have to query on other parameters without affecting the performance. Nested dictionaries is “an” answer.(Not always though, if you need perform comparison)

A sample declaration is as follows

   Dim dict As Dictionary(Of Integer, Dictionary(Of Long, Dictionary(Of Integer, Dictionary(Of String, Product))))

Now creating such a nested dictionary is not an easy work. For Each column, For Each order, you have tailor the dictionary and there is no “abstract” silver bullets to ease the creation of such a dictionary.
As for future convenience I just created a simple extension to IEnumerable which will convert to a “MultiKeyDictionary”.

Here is a simple walkthrough:

Dim students As New List(Of Student)
students.Add(New Student() With {.Age = 10, .Batch = 1231456, .Name = "Tony", .Year = 2012})
students.Add(New Student() With {.Age = 11, .Batch = 1231435, .Name = "James", .Year = 2013})
students.Add(New Student() With {.Age = 10, .Batch = 1231456, .Name = "Andy", .Year = 2012})

And all you need to do is.

dict = students.ToMultiKeyDictionary({Function(x) x.Age, Function(x) x.Batch, Function(x) x.Year, Function(x) x.Name, Function(x) x})

I have made this for fun and cant guarantee that this will be bug free. If you find some issues please do report.

The gist to the implementation is available at : https://gist.github.com/1845121

<ExtensionAttribute()>
 Public Function ToMultiKeyDictionary(Of TSource, TSecKey)(ByVal source As IEnumerable(Of TSource), ByVal ParamArray func() As Func(Of TSource, TSecKey)) As IDictionary
 Dim reverseCollection As New Dictionary(Of Integer, Type)
 Dim functionsCount As Integer = func.Count
 Dim dictTypeCollection As New List(Of IDictionary)
Dim instanceOfCurrentDictionaryArgument As New Object
 Dim dictionaryArgQueue As Object = Nothing
 Dim dictionaryTypes As New List(Of Type)
 For i = 0 To functionsCount - 1
 reverseCollection.Add(i, source.Select(func(func.Count() - (i + 1))).FirstOrDefault().GetType())
 Next
 Dim typeOfGenericDictionary As Type
 Dim dictionaryTypeArgs() As Type
 Dim newConstructedType As Type
 Dim currentDictionaryInstance As IDictionary
For i = 0 To reverseCollection.Count - 1
If reverseCollection(i) IsNot GetType(String) Then
instanceOfCurrentDictionaryArgument = Activator.CreateInstance(reverseCollection(i))
Else
 instanceOfCurrentDictionaryArgument = String.Empty
End If

If instanceOfCurrentDictionaryArgument IsNot Nothing AndAlso dictionaryArgQueue IsNot Nothing Then
typeOfGenericDictionary = GetType(Dictionary(Of ,))
 dictionaryTypeArgs = {instanceOfCurrentDictionaryArgument.GetType(), dictionaryArgQueue.GetType()}
 newConstructedType = typeOfGenericDictionary.MakeGenericType(dictionaryTypeArgs)
 currentDictionaryInstance = Activator.CreateInstance(newConstructedType)
 dictionaryTypes.Add(newConstructedType)
 dictionaryArgQueue = currentDictionaryInstance
 Else
 dictionaryArgQueue = instanceOfCurrentDictionaryArgument
 End If
 Next
 Dim dict As IDictionary = Activator.CreateInstance(dictionaryTypes.Last())
 'If dict IsNot Nothing Then
 Dim queueObj As Object
 For Each item In source
Dim currentItem = item

Dim itemAddedFlag As Boolean = False
 Dim currentItemInCollection As IEnumerable(Of TSource)
Dim iterDict As IDictionary = dict 'Reset dictionary
For i = 0 To func.Count - 1
 currentItemInCollection = {currentItem}
Dim currentKey As TSecKey = currentItemInCollection.Select(func(i)).FirstOrDefault()
If iterDict.Contains(currentKey) AndAlso iterDict(currentKey) IsNot Nothing Then
 iterDict = iterDict(currentKey)
 Else
queueObj = Nothing
 For k = 0 To reverseCollection.Count - 1
If queueObj Is Nothing Then
queueObj = currentItemInCollection.Select(func(functionsCount - (k + 1))).FirstOrDefault()
Else
Dim curObject As Object = currentItemInCollection.Select(func(functionsCount - (k + 1))).FirstOrDefault()
 Dim currentDictionaryFromReverse As IDictionary = Activator.CreateInstance(dictionaryTypes(k - 1))
 If iterDict.GetType() = currentDictionaryFromReverse.GetType() Then
iterDict.Add(curObject, queueObj)
 itemAddedFlag = True
 Exit For
End If
 currentDictionaryFromReverse.Add(curObject, queueObj)
 queueObj = currentDictionaryFromReverse
End If
Next
If itemAddedFlag Then
 Exit For
 End If
 End If
Next
 Next
Return dict
End Function

Create a free website or blog at WordPress.com.

Up ↑