Speed tip with popupmenus

In a different forum a user reminded me of a speed tip that had cropped up once or twice over the years. It has to do with popupmenus.

Basically on older versions of macOS if you did


PopupMenu1.ListIndex = -1
For i As Integer = 0 To 1000
  PopupMenu1.AddRow Str(i)
Next

This behaved not so badly although there is a slightly faster variation

But on Mojave this is noticeably slow. This simple loop could take as much as a second on Mojave in dark mode.

To speed it up it was discovered that if you added an item in position(0), set the listindex to 0 then added all the items and removed that extraneous item at position 0 then the speed was hardly impacted at all.

PopupMenu2.AddRow ""
PopupMenu2.ListIndex = 0
For i As Integer = 0 To 1000
  PopupMenu2.AddRow Str(i)
Next
PopupMenu2.RemoveRow 0

My testing on 10.12.6 and 10.14.5, in light and dark modes, using 2019r1.1 with the following code shows the differences in the debugger

Sub Action()
  Dim startTime1 As Double 
  Dim endTime1 As Double
  
  Dim startTime2 As Double 
  Dim endTime2 As Double
  
  
  startTime1 = Microseconds
  PopupMenu1.ListIndex = -1
  For i As Integer = 0 To 1000
    PopupMenu1.AddRow Str(i)
  Next
  endTime1 = Microseconds
  
  
  startTime2 = Microseconds
  PopupMenu2.AddRow ""
  PopupMenu2.ListIndex = 0
  For i As Integer = 0 To 1000
    PopupMenu2.AddRow Str(i)
  Next
  PopupMenu2.RemoveRow 0
  endTime2 = Microseconds
  
  
  Dim total1 As Double = (endtime1 - startTime1) / 1000000
  Dim total2 As Double = (endtime2 - startTime2) / 1000000
  
  label1.text = str(total1, "###.00000")
  label2.text = str(total2, "###.00000")

End Sub

And the results are surprising and consistent between running in the debugger & a built application

Debug              Total1 (slow)       Total2 (fast)
 10.12.6            0.037 sec            0.012 sec
 10.14.5 (light)    1.152 sec            0.019 sec
 10.14.5 (dark)     1.158 sec            0.019 sec 
 Built x86_64       Total1 (slow)       Total2 (fast)
 10.12.6            0.036 sec            0.010 sec
 10.14.5 (light)    1.174 sec            0.017 sec
 10.14.5 (dark)     1.174 sec            0.028 sec

Whatever changed in Mojave Popupmenus got very slow.

Heads up.

Thanks to Jim McKay for reporting this

9 thoughts on “Speed tip with popupmenus”

  1. Can you test:
    PopupMenu1.ListIndex = -1
    For i As Integer = 0 To 1000
    PopupMenu1.AddRow Str(i)
    PopupMenu1.ListIndex = 0
    Next
    PopupMenu1.ListIndex = -1

    and comment about the results? Thanks

    1. You could but I suspect setting listindex on every iteration of the loop would simply make things slower than they need to be

      1. On my mac with 10.12.6 debug:
        Total 1 = 0.01742
        Total 2 = 0.00547
        Total 3 = 0.00776
        I suspected slower speed than listindex -1 (Total 1) but is just a little slower than the addrow/removerow workaround (Total 2).

        Remote debug to Mojave VM:
        Total 1 = 0.97851
        Total 2 = 0.01481
        Total 3 = 0.01587

  2. Hi Norman! Nice tip, but why not:

    PopupMenu2.AddRow Str(0)
    PopupMenu2.ListIndex = 0
    For i As Integer = 1 To 1000
    PopupMenu2.AddRow Str(i)
    Next

    1. This would work assuming you wanted to retain the 0th element
      Basically avoiding the listindex = -1 setting until after the list was loaded has the best impact on speed

      1. Well, if you want to fill the PopupMenu then you might as well use the data you want to fill it with. So there is no need to remove the 0th element.

        As an extension method I would write it something like this:

        Public Sub SetToArray(Extends pop As PopupMenu, arr() As String)

        Dim NumberOfArrayElements As Integer = arr.Ubound

        Select Case True

        Case NumberOfArrayElements > 1

        pop.AddRow arr(0)
        pop.ListIndex = 0

        For i As Integer = 1 To NumberOfArrayElements
        pop.AddRow arr(i)
        Next

        Case NumberOfArrayElements = 1

        pop.AddRow arr(0)
        pop.ListIndex = 0

        Case NumberOfArrayElements = 0

        MsgBox “Oops!”

        End Select

        End Sub

  3. I made two more additions to the test:

    Method 3 with my modification as mentioned above
    Method 4 with the code in its own extension method

    Public Sub SetToArray(Extends pop As PopupMenu)
    pop.AddRow Str(0)
    pop.ListIndex = 0
    For i As Integer = 1 To 1000
    pop.AddRow Str(i)
    Next
    End Sub

    The results (average of 10 runs) are from the build app and are pretty consistent:

    On High Sierra:

    Method 1: 0.05144
    Method 2: 0.01422
    Method 3: 0.01440
    Method 4: 0.01186

    Mojave (different computer, single-core performance is about twice as fast):
    Method 1: 1.28073
    Method 2: 0.01274
    Method 3: 0.01104
    Method 4: 0.00999

    Interestingly putting the code in its own method speeds it up by 10-20%. I would have thought the overhead of the method call would slow it down slightly, but it might be worth it for the convenience, but the speedup was unexpected. Maybe due to better optimization?

    Complete code is:

    Sub Action() Handles Action
    #Pragma BackgroundTasks False
    #Pragma BoundsChecking False
    #Pragma NilObjectChecking False
    #Pragma StackOverflowChecking False

    Dim total1 As Double
    Dim total2 As Double
    Dim total3 As Double
    Dim total4 As Double

    Dim NumberOfRuns As Integer = 10
    Dim Counter As Integer

    For Counter = 1 To NumberOfRuns

    PopupMenu1.DeleteAllRows
    PopupMenu2.DeleteAllRows
    PopupMenu3.DeleteAllRows
    PopupMenu4.DeleteAllRows

    Dim startTime1 As Double
    Dim endTime1 As Double

    Dim startTime2 As Double
    Dim endTime2 As Double

    Dim startTime3 As Double
    Dim endTime3 As Double

    Dim startTime4 As Double
    Dim endTime4 As Double

    startTime1 = Microseconds
    PopupMenu1.ListIndex = -1
    For i As Integer = 0 To 1000
    PopupMenu1.AddRow Str(i)
    Next
    PopupMenu1.ListIndex = 0
    endTime1 = Microseconds

    startTime2 = Microseconds
    PopupMenu2.AddRow “”
    PopupMenu2.ListIndex = 0
    For i As Integer = 0 To 1000
    PopupMenu2.AddRow Str(i)
    Next
    PopupMenu2.RemoveRow 0
    endTime2 = Microseconds

    startTime3 = Microseconds
    PopupMenu3.AddRow Str(0)
    PopupMenu3.ListIndex = 0
    For i As Integer = 1 To 1000
    PopupMenu3.AddRow Str(i)
    Next
    endTime3 = Microseconds

    startTime4 = Microseconds
    PopupMenu4.SetToArray
    endTime4 = Microseconds

    total1 = total1 + (endtime1 – startTime1)
    total2 = total2 + (endtime2 – startTime2)
    total3 = total3 + (endtime3 – startTime3)
    total4 = total4 + (endtime4 – startTime4)

    Next

    total1 = total1 / (1000000 * NumberOfRuns)
    total2 = total2 / (1000000 * NumberOfRuns)
    total3 = total3 / (1000000 * NumberOfRuns)
    total4 = total4 / (1000000 * NumberOfRuns)

    label1.Text = Str(total1, “###.00000”)
    label2.Text = Str(total2, “###.00000”)
    label3.Text = Str(total3, “###.00000”)
    label4.Text = Str(total4, “###.00000”)

    Result = “Method 1: ” + Str(total1, “###.00000”) + EndOfLine + _
    “Method 2: ” + Str(total2, “###.00000”) + EndOfLine + _
    “Method 3: ” + Str(total3, “###.00000”) + EndOfLine + _
    “Method 4: ” + Str(total4, “###.00000”)

    End Sub

    Public Sub SetToArray(Extends pop As PopupMenu)

    pop.AddRow Str(0)
    pop.ListIndex = 0
    For i As Integer = 1 To 1000
    pop.AddRow Str(i)
    Next
    End Sub

  4. It wasn’t so much to find the optimal way to do this just to publicise a rather curious effect that doing that might seem normal, setting listindex = -1, suddenly had a noticeable and measurable side effect on Mojave.
    Ideally the framework would be fixed to NOT do whatever is causing this.

  5. A good question was posed to me elsewhere
    >norman , does the speed tip for popup menu work for dropdown/combobox?
    It appears to NOT have the same huge slowdown as the popup menu when doing the same addition of rows to a combobox

Comments are closed.