Gaffi
Gaffi

Reputation: 4367

Iterating through a Scripting.Dictionary/Collection object

I have a project that's been changed and modified in bits and pieces over the past few years, and much from one code module to the next is non-standardized. In some cases, I have a Scripting.Dictionary object used and in others, I have a Collection object. For each of these, there are sometimes iterations by count (i.e. For i = 1 to Obj.Count) and sometimes by For...Each.

I would like to apply the same logic for as many of these cases as possible to make future changes hopefully more seamless, but I'm not sure which methodology is best. (I believe some specific cases made be made for one or another method, but I'm also pretty sure some of the code could use any of the methods available.)

I tried creating a testing sub to help me determine which of these methods works best, but the results were somewhat inconsistent. Overall, it appears that looping through each Item in a Dictionary is faster, but in some cases my tests came out showing a loop through each Item in a Collection is faster. The variance probably depends on everything else that's going on in the system at any given time.

I was wondering if anyone had a definitive answer as to which method is consistently the fastest, assuming all else within the loop is the same. Or, is there some way to improve my testing sub to be more consistent when returning results so that I can answer this question myself?

The testing code I've come up with:

Option Explicit

Sub Test_Dictionary_Iteration_Speed()

Dim Coll1 As New Collection, Coll2 As New Collection
Dim Dict1 As New Scripting.Dictionary, Dict2 As New Scripting.Dictionary, Dict3 As New Scripting.Dictionary
Dim i As Integer, j As Integer, l As Integer
Dim StartTime As Single, StopTime As Single
Dim v As Variant
Dim Obj As TestObject 'A custom Class that has only one member variable, MainVal, and no functions/subs

    For i = 0 To 32766
        Set Obj = New TestObject
        Obj.MainVal = i
        Dict1.Add CStr(i), Obj
        Dict2.Add CStr(i), Obj
        Dict3.Add CStr(i), Obj
        Coll1.Add Obj, CStr(i)
        Coll2.Add Obj, CStr(i)
    Next i

    StartTime = Timer()

    For j = 0 To Dict1.Count - 1
        l = CInt(Dict1(CStr(j)).MainVal)
        Set Obj = Dict1(CStr(l)) 'Do something useful within the loop
        Set Obj = Nothing
        Dict1.Remove CStr(l)
    Next j

    StopTime = Timer()

    Debug.Print "Dict1 for x to y: " & StopTime - StartTime

    StartTime = Timer()

    For Each v In Dict2.Items
        l = CInt(v.MainVal)
        Set Obj = Dict2(CStr(l))
        Set Obj = Nothing
        Dict2.Remove CStr(l)
    Next v

    StopTime = Timer()

    Debug.Print "Dict2 for each item: " & StopTime - StartTime

    StartTime = Timer()

    For Each v In Dict3.Keys
        l = CInt(Dict3(v).MainVal)
        Set Obj = Dict3(CStr(l))
        Set Obj = Nothing
        Dict3.Remove CStr(l)
    Next v

    StopTime = Timer()

    Debug.Print "Dict3 for each key: " & StopTime - StartTime

    '---------- Division between Dictionary and Collection

    StartTime = Timer()

    For j = 0 To Coll1.Count - 1
        l = CInt(Coll1(CStr(j)).MainVal)
        Set Obj = Coll1(CStr(l))
        Set Obj = Nothing
        Coll1.Remove CStr(l)
    Next j

    StopTime = Timer()

    Debug.Print "Coll1 for x to y: " & StopTime - StartTime

    StartTime = Timer()

    For Each v In Coll2
        l = CInt(v.MainVal)
        Set Obj = Coll2(CStr(l))
        Set Obj = Nothing
        Coll2.Remove CStr(l)
    Next v

    StopTime = Timer()

    Debug.Print "Coll2 for each item: " & StopTime - StartTime

    Debug.Print vbNewLine & "-----" & vbNewLine   


End Sub

Real examples of output, showing that the 'best' option is not always the same:

Dict1 for x to y: 0.2011719
Dict2 for each item: 0.1738281
Dict3 for each key: 0.2167969
Coll1 for x to y: 0.2050781
Coll2 for each item: 0.1386719


Dict1 for x to y: 0.1875
Dict2 for each item: 0.171875
Dict3 for each key: 0.234375
Coll1 for x to y: 0.2050781
Coll2 for each item: 0.1542969


Dict1 for x to y: 0.25
Dict2 for each item: 0.21875
Dict3 for each key: 0.265625
Coll1 for x to y: 0.234375
Coll2 for each item: 0.171875


Dict1 for x to y: 0.265625
Dict2 for each item: 0.203125
Dict3 for each key: 0.296875
Coll1 for x to y: 0.234375
Coll2 for each item: 0.21875


Dict1 for x to y: 0.265625
Dict2 for each item: 0.1875
Dict3 for each key: 0.234375
Coll1 for x to y: 0.203125
Coll2 for each item: 0.15625


Dict1 for x to y: 0.28125
Dict2 for each item: 0.1875
Dict3 for each key: 0.25
Coll1 for x to y: 0.234375
Coll2 for each item: 0.1875


Dict1 for x to y: 0.28125
Dict2 for each item: 0.21875
Dict3 for each key: 0.328125
Coll1 for x to y: 0.234375
Coll2 for each item: 0.234375

Upvotes: 2

Views: 5083

Answers (1)

Unless you know for a fact that the execution time of a given statement or procedure is a problem, you shouldn't be wasting your human time on optimization. First design and debug, then if you think things are too slow (which they probably won't be), profile, and only then optimize (execution time will typically be wasted somewhere completely different than you thought).

The For Each construct is nice and concise and tidy. The only reason not to use it is if you're deleting items from a collection being looped over. Then you risk skipping over certain items. If you're planning on deleting items, loop over the index, backwards.

Upvotes: 2

Related Questions