Code Profiler

The Code Profiler is used to track the length of time each method in your app takes to run. This information allows you to focus on performance optimizing the parts of your app that might be considered slow.

When profiling for performance, remember the quote by famous computer scientist Donald Knuth:

"The real problem is that programmers have spent far too much time worrying about efficiency in the wrong places and at the wrong times; premature optimization is the root of all evil (or at least most of it) in programming."

This means that you should not worry about performance until you have a reason to worry about performance. The good news is that when you are ready to worry about performance, the Xojo Code Profiler provides an easy way to identify the areas of your code that take longer to run.

Note

Code profiling is not currently supported for Android.

Using the Code Profiler

You can enable or disable the Profiler using the Project > Profile Code menu. A check mark appears next to the menu when profiling is enabled. After enabling Profile Code, you run and test your apps as you normally would. The Code Profiler silently gathers information about the methods that are called and how long each one takes to execute. Be aware that your application runs slower than usual when profiling is enabled due to the overhead of tracking and timing everything to get the profiler data.

When you quit your app, the Profiles section appears in the Navigator with an entry for the profile data.

Each time you run your project, the profile results appear in this Navigator section as a separate item. This allows you to compare current results to prior results. You can remove profile data by right-clicking on it in the Navigator and selecting Delete from the menu. Click on a Profile Data in the Navigator to display the Profile Results list, which shows you a list of all the methods that were called while you were using your app. It displays the number of times each method was called and how much time was spent in the method.

  • Name: The class name and name of method. The thread is also displayed if your app uses threads.

  • Called: The total number of times the method was called.

  • Total: The total time (in milliseconds) that was spent in the method.

  • Average: The average time (in milliseconds) that was spend in the method. This is Total / Called.

You can save the profiler data to a text file using the contextual menu. Right-click anywhere in the summary and choose “Save As...”.

You can expand methods to see the other methods that they called.

When a method is collapsed, the time shown represents the time used by the method and any methods that were called as a result of that method running.

When a method is expanded, the time shown for the method represents the time used only by the method itself. Any called methods are shown below it and have their own time values.

As you are optimizing your app, you can compare the current Profiler Data with previous Profiler Data to see if your changes are improving performance. Profiler data is not saved along with your project. Use the Save As function described earlier if you wish to retain the profiler data.

Profiler data is only collected if your app quits normally. If it quits because of a crash (or you press the Stop button in the debugger), no profiler data is collected. For web apps you can close all the browser windows/tabs and wait for the IDE to detect that the app has terminated using code like this:

App.AutoQuit = True
App.SessionTimeout = 10

Alternatively you can add a button or action that calls the WebApplication.Quit method.

Profiler data cannot be collected for Mac apps that have been sandboxed.

The Code Profiler does not work with the Remote Debugger. If you need to use the Profiler to gather information about your app running on another platform, just install Xojo on the platform, copy your project over to it and run it from there with Profile Code enabled.

The Code Profiler does not work with iOS projects.

Using the profiler with built apps

You can choose to create a standalone build of your app with embedded profiling code. You can then send this app to the users so that they can help generate profile data for you to analyze. To do this, just build your app with Profile Code selected in the Project menu. When you build with Profile Code enabled, a dialog appears to remind you that profiling code will be embedded in the app.

With this profiling code embedded, your app will log profiling data as it is being used. When it quits, the profile data is saved to a file called Profile.txt in the same folder as the app. Your user can then send you this file for you to analyze.

To view saved profiler data, take advantage of the open-source Profile-Reader project.

Note

If your app is crashing, your profile data will not be saved. You can force it to be saved by calling the SaveProfile method.

Controlling the profiler from code

When the Profile is enabled from the Project > Profile Code menu, you can selectively turn it off and back on from within your code. To turn profiling off, use the StopProfiling method and to turn it back on, use the StartProfiling method.

If you want to only profile a small set of methods in your app, turn off profiling in the App.Opening event with StopProfiling and then turn it on at the start of methods you want to profile and off at the end of the method.

Performance analysis tips

When reviewing the results, each column tells you useful information, but not all of this information indicates you have a performance problem. Here are some suggestions of things to look out for.

Note

Pausing a thread does not reduce profile measurement time.

High number of calls

You should evaluate methods that have a high number of calls. Try to determine if the method needs to be called so often. Reducing the number of times a method gets called can improve performance.

For example, perhaps you have a Refresh method that is called often. In reviewing this, you may realize that the Refresh method is calling other methods unnecessarily and can perhaps be simplified. Or perhaps you'll notice that Refresh is being called more often than necessary.

High total time

It is worth reviewing methods that have a high Total Time. Now it could be that the Total Time is high because the method is called frequently. But this still warrants a review. It could be that the time is high because the method take a long time to complete, but this will mean it also has a high Average Time.

Regardless, these methods are worth reviewing to see if they can be improved.

High average time

Methods with high Average Time can quickly degrade performance if they are called often. Average Time is Total Time / Calls, so you may already be reviewing these methods. Often, a simple improvement to a method that is called frequently can have a dramatic effect on the entire app.

Optimization techniques

To re-iterate, you should not spend your time optimizing code unless there is a clear case that it needs to be optimized. Sometimes it is just more pragmatic to move long-running code to a thread so that the user does not notice how long it takes. There may also be situations where the time spent optimizing the code does not yield a significant enough improvement to warrant the time spent on it.

But if you do run into situation where your code is running more slowly than needed, these techniques may help.

Remove unnecessary loop calculations

If you have a calculation that occurs on a loop condition, then the calculation is done each time through the loop. Sometimes this may be necessary because the value could change. But often, this value does not change (it is what is called invariant -- it never varies), so you can instead save the value in a variable so that it does not get recalculated each time.

In this example, the tax rate is calculated each time through the loop. But the tax rate does not change while the loop is running, so the calculation is peformanced unnecessarily:

While (billAmount * TaxRate(state)) < 100
  AddLineItemToBill
Wend

Instead, you can save the tax rate:

Var taxRate As Double = TaxRate(state)
While (billAmount * taxRate) < 100
  AddLineItemToBill
Wend

ListBoxes

Populating ListBoxes with data is a common task. If you are filling a ListBox with lots of data (which itself may not be a great idea), you can first make the ListBox invisible before you add the rows and then make it visible after the rows have been added. This can prevent some unnecessary possible screen refreshes and resulting event calls, providing an overall speed improvement.

ListBox1.Visible = False
' Do long-running AddRow loop here
ListBox1.Visible = True

Call fewer methods

There is a small amount of overhead for each method call. In a loop, you may find that just putting the code directly into the loop, rather than it its own method will result in a speed improvement. This should be done judiciously as it can quickly lead to unreadable code.

You might also consider using framework methods that can do multiple things in one method call, rather than multiple method calls. For example, with a ListBox using AddRow to add a blank row and then filling its columns using the Cell method is less efficient than just calling AddRow once with all the cell data:

' Inefficient
MyListBox.AddRow("")
MyListBox.CellTextAt(MyListBox.LastAddedRowIndex, 0) = "Col0"
MyListBox.CellTextAt(MyListBox.LastAddedRowIndex, 1) = "Col1"
MyListBox.CellTextAt(MyListBox.LastAddedRowIndex, 2) = "Col2"
MyListBox.CellTextAt(MyListBox.LastAddedRowIndex, 3) = "Col3"

' More efficient
MyListBox.AddRow("Col0", "Col1", "Col2", "Col3")

Var outside loops

Xojo allows you to using the Var keyword to declare variables within code blocks, including loops. This can be handy for code organization and it is usually recommended. But you can get a speed improvement by declaring a variable outside the loop and re-using it within the loop instead.

While someConditionIsTrue
  Var s As String = CallMethod
Wend

By writing it this way instead, your loop can save some time:

Var s As String
While someConditionIsTrue
  s = CallMethod
Wend

Loop analysis

As several of these tips have noted, improving loops is often the path to code optimization. Because loops repeat many times, something slow within a loop is magnified many times. So improving something within a loop can sometimes result in a signicant speed improvement.

Use a different algorithm or technique

Sometimes your algorithm is just slow and performance tuning will not help enough to matter. For example, if you are searching data sequentially no amount of performance tuning is likely to help significantly. It will likely be faster to first sort the data and then do a binary search on the results. Or it may make sense to put data into an in-memory database and use its capabilities for fast searching.