In my last blog post I described how to use Firebug’s Profiler in order to find out which parts of your code are most time consuming and are responsible for the performance issues you might experience. Today I am showing you how I used the Profiler as a step in the iterative process of tuning the performance of a web app.
Steps of the iterative performance tuning process:
According to Wikipedia, the systematic tuning of performance comprises the following steps:
- Assess the problem and establish numeric values that categorize acceptable behavior.
- Measure the performance of the system before modification.
- Identify the part of the system that is critical for improving the performance. This is called the bottleneck.
- Modify that part of the system to remove the bottleneck.
- Measure the performance of the system after modification.
- If the modification makes the performance better, adopt it. If the modification makes the performance worse, put it back the way it was.
- start at 1. again
The Problem & Performance before the test
In my application, the performance issues concerned the searching functionality. I have a list with more than 45000 items which is searched for a search term the user can enter in a search field. The search itself uses an implementation of the Soundex algorithm (I described the Soundex Algorithm and the adoptions I made in order to use it for the German language in an earlier post) to match the search term against the items. I observed the performance to be awful especially on mobile devices.
I did not establish a numeric value to define acceptable behaviour. I just wanted it to improve significantly (being well aware that “improve significantly” is a heavy violation of the SMART principle…) so that the user does not deem the application dead while waiting for the search to finish…
Repeatedly running the Firebug profiler gave me the average of about 4500ms per searching task.
Identifying and removing the bottleneck
Using the Firebug profiler, I received the following profiling report (excerpt):
With the help of this report, I could easily identify the first three function calls as the bottleneck of my search. The first of them is an access to the database to retrieve all items of the list, the second the transformation of a search term into its Soundex code, and the third a filtering operation.
Generally, there are two options to deal with these functions: either to improve the function so that less time is spent for its execution, or to reduce the number of calls of that function; which of the two options is best of course depends on the function’s internal logic.
I started by analyzing the first of the functions and checked where it was called. I found out that accessing the database could be prevented entirely here. Until now, the same variable was used for storing the list as it appears when it is not filtered, and for storing the list when it contains only the search results. So what happened during each search was that first, the list got emptied completely. Then, a list with all available items was demanded from the database and then filtered for the search item. I introduced a separate variable to store the list with the unfiltered items. Now the search can use this variable instead of retrieving the list from the database.
Assessing the improvement
Having made this modification, I ran the profiler again in order to check if the performance has indeed improved. As I had expected, the function that accesses the database was not invoked a single time, leading to an improvement of about 1500ms! This was a great success, so I kept the changes.
I also had a look at the other two bottlenecks. I could not improve the Soundex algorithm, nor could I reduce the amount of calls of that function. However, I was able to make some further improvement by making some minor changes to the filterBy function. Even though the modification decreased the average time spent in this function by only about 0.015ms, this accounts for quite a lot if it is multiplied with 45426, the number of calls of this function.
In total, a sorting task now takes about half the time it did before. Here is an excerpt of the profiling report after the tuning: