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
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
You could but I suspect setting listindex on every iteration of the loop would simply make things slower than they need to be
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
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
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
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
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
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.
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