Performance Optimization
I would like record here, while it is fresh in my mind, the saga of how @folkertdev helped to make a crucial performance optimization in the text editor project that I am working on. In my case, it made the difference between the editor being very laggy and a pain to use on documents of over about 1000 words, and being rather snappy.
I believe that this is a topic of general interest and is important for building performant apps. Many in the community have something to say on the subject. Perhaps we could gather this knowledge into a single shared document that others could use
The Saga
Here is the story. I mentioned my problem to Folkert de Vries in a private chat, describing the lagging problem and asking for some advice on what turned out to be a completely wrong way of addressing the underlying issue. A few minute later Fokert wrote back saying that my app was spending a lot of time in the function ensureNonBreakingSpace
, which is defined this way:
ensureNonBreakingSpace : Char -> Char
ensureNonBreakingSpace char =
if char == ' ' then
nonBreakingSpace
else
char
He suggested using instead this function:
ensureNonBreakingSpace : Char -> Char
ensureNonBreakingSpace char =
case char of
' ' ->
nonBreakingSpace
_ ->
char
This small change turned the app from laggy to snappy.
The Method
One might view this saga as an instance of magic, but it instead a combination of art and science.
Step 1: Profile
Always do this! Your hunches, if you have any, will often be wrong, even wildly wrong. Here is how to do it.
-
Open your app in Chrome. Then open devtools to the
Performance
tab. -
Click on the record button of the Performance tab.
-
Play around with the app.
-
Stop recording, and view the results using the Bottom-Up tab. Here is a screenshot from devtools:
Step 2: Think about it
You will notice that the function _Utils_eqHelp
is taking up 28.4% of the execution time. To find out who is calling this function, click on the triangle to the left of this function. Here is what one sees:
Scanning down, one finds $jxxcarlson$elm_text_editor
followed by $Editor$View$ensureNonBreakingSpace
. This is the Javacript incarnation of the Elm function defined this way in module Editor.View
:
ensureNonBreakingSpace : Char -> Char
ensureNonBreakingSpace char =
if char == ' ' then
nonBreakingSpace
else
char
Seems rather innocuous, no? But it is in fact the culprit. Folkert suggested using this code instead:
ensureNonBreakingSpace : Char -> Char
ensureNonBreakingSpace char =
case char of
' ' ->
nonBreakingSpace
_ ->
char
This does not seem like much of a change, but under the hood, it is because checking for equality is expensive. When I replaced my code by Folkert’s, the change was evident: laggy had been replaced by snappy. When I profiled the code a second time, _Utils_eqHelp_
did not appear on the first page. Success!
Here was my sign-off to Folkert:
Wow! I tried your version of
ensureNonBreakingSpace
— it results in a HUGE performance gain. The marker for that function doesn’t even show up on the first page of the performance results. Number 1 isMinorGC
at 31% and number 2 is_VirtualDom_diffFacts
at 7.7%. From the user perspective: snappy editor performance on the 3000 word file.
Conclusions and comments
-
Profile to find where the real bottlenecks are. Don’t optimize before you know this!
-
Work to understand why the bottleneck code is taking such a great share of the execution time. Understanding now the compiled Javascript code works is a big help.
-
Optimizations are often needed to make apps performant and pleasant to use.
Perhaps at some future date, the compiler will be able to do optimizations of this sort.