Performance in Flutter What is it? How is it measured? How is it interpreted? How is it improved?
Once I was watching Star Wars, and Yoda said, “If you want to control your performance in Flutter, measure it, you must.”
Well, speaking strongly, Flutter has excellent performance, but sometimes due to development errors, we can accidentally fall into practices that impact the performance of our application. We may notice it due to latency when drawing elements on the screen, but the error is not noticeable to the human eye in many other circumstances. For example, if your application uses more battery power than expected, occupies more space than expected in memory, or has some strange behavior before some animations, it indicates that you should measure the application’s performance.
So let’s navigate this somewhat deep topic 🚤 I hope you like this article, don’t forget to leave us about +50 applause and comments at the end if you have any concerns.
What is performance?
Well, we must start by defining the topic we are analyzing. We can understand performance as a set of quantifiable properties of an executable. In our context, it is not the execution of the action itself. It is how well it is done when performing it.
I highlight quantifiable because it is vital to have a number representing the ability to perform the task. For example, the size of your final application is a measure that can be assigned to a number, and in this way, it is clear that if a new feature of your App makes it go from 120 MB to 200 MB, it is an indication that something may be wrong. And maybe you have opportunities for improvement.
Therefore, all elements to measure performance must be quantifiable.
Because it is essential?
We must measure performance automatically to ensure that our applications provide the best customer experience. An application performance issue is usually associated with implementation issues, not the SDK.
However, not everything is visible to the human eye. For example, FPS is the unit that measures the number of frames a device generates or processes during 1 second. The idea is that our applications work at a minimum of 60 FPS. Still, the human eye only distinguishes a little more than 25 FPS , so it would be challenging for us to measure a good performance using only our senses. Wrong rendering time implies an unnecessary drain on the GPU, leading to a higher drain on the battery.
Therefore, if we do not measure our performance, we will dedicate ourselves to putting out fires instead of guaranteeing the quality of our developments before going into production.
Items to keep in mind
There are five points you should have on your radar when measuring performance:
- It would help if you made the metrics easy to understand 📈. You should always measure numbers so developers can know when it’s a performance improvement or an impact.
- Make specific metrics🧐. By this, I mean that a unit must be established and define the standard for that measurement. For example, all our apps must run at a minimum of 60 FPS (there’s nothing ambiguous about that, and there’s no room for developer interpretation).
- Make performance easy to compare. For example, holding the old measurement against the new one can help identify performance gains or losses.
- We must make performance metrics monitor as broad a population as possible so that nothing is left behind. In this way, we guarantee the excellent performance of the App.
- You should not perform performance optimizations until a test shows that there is indeed a problem. It happens because sometimes we believe that certain developments can improve performance. Still, we should not rush to correct supposed flaws until we corroborate with tests that there is an error in a specific part of our implementations.
We can divide performance into two main categories, namely time and space. In the time category, we can see measurements such as FPS, time to first frame drawn, etc. And in the space category, we will see elements such as application size, memory consumed by a process, etc.
How can we measure performance in our Flutter apps?
We have reached the moment many of us were eager to get, and it is time to apply the theory and learn about performance measurement tools.
First of all, I must mention a vital issue Flutter, by default, has good performance, so if we notice an error in our App, most likely the error is on our side. To help with this issue, he has given us a powerful tool to measure our performance and even be able to diagnose the root of our problem. And this is profile mode. To enter this mode, you can execute the following command:
flutter run --profile
Note: Keep in mind this command must be run on a physical device. You must have your certificates and the permissions of your cell phone properly configured. Remember that to execute this command.
If you have several devices connected, you must choose the one you want among them.
When the process finishes, you will see something like in the following image:
These URLs will take you to two tools: One is an observatory setup on the configured phone:
In this observatory, you will be able to observe the current memory consumption and the highest peaks that have occurred while using the application. However, Google gives you a second tool in which you can evaluate several points that can be very interesting, so we are going to see the second link, which is our well-known developer tool.
These two tools are the ones we will use to measure our performance.
How is it interpreted?
Let’s test these tools with a demo app 🧐:
For this, I have designed a small example in which we draw ten thousand widgets on the screen, in the first scenario with a lousy code (Low Perfo) and a second scenario with a code with better practices (High Perfo). We also have a screen to test our internet consumption. Finally, we will generate two builds and compare their sizes.
Let’s start by looking at the two codes. First low_perfo.dart:
As we see in this code, there are several bad practices . On the one hand, we have a Column drawing 10000 widgets. It will be impossible to display elements on the screen until Flutter draws them. On the other hand, we have a function that returns devices, which is also a lousy practice 🥲. In short, this solution should have a bad performance. I exaggerated the bad implementation a bit to make the error more evident.
On the other hand, we have the high_perfo.dart implementation:
In this implementation, we have a ListView.Builder that helps us to build everything lazily. That is, only those elements with which the user interacts are made. Therefore, the difference versus the previous code should be significantly apparent. But will it be that way?
With these two codes ready, I started my performance tests:
Let’s first see what our first tool (the observatory) returned:
From now on, we can see in the observatory that there is high memory consumption of the device when entering the experience, which we deduce would have poor performance. However, if we want to see more detail, it would be perfect if we examined the developer tools.
First, let’s look at the performance tab:
However, as some graphs are shown on the screen, what do these bar graphs mean?
The UI thread executes the Dart code in the Dart VM. This thread generates the layer tree. Remember that Flutter runs widgets and elements and renders object trees parallel to draw the details on the screen. (I suggest you read the following article if you don’t have this link context). The layer tree has the elements to draw, but it doesn’t know how to do it, so it sends its information to the raster thread.
The raster thread takes the layer tree exposed in the UI thread and then sends the necessary commands to the GPU to draw the elements on the screen.
Jank (slow frame)
A frame that has taken a long time to draw to the screen is considered a Jank. As a standard, anyone who brings more than 16 ms (for devices running at 60 FPS) to finish their draw is considered a Jank.
Frames that perform shader compilation are marked in dark red.
With these concepts in mind, we can understand the graphs displayed by the performance tab. As we can see when drawing on the screen, the high-performance experience does not present any anomaly to worry about since no frame takes more than 17 ms to finish its process.
Now let’s see the low-performance experience:
As we see for this screen, we have several Jank or slow frames. At this moment, we can deduce that it was costly to create the layer tree. Therefore the thread involved has a long occupation time.
If we click on one of the bars that indicates that there is a Jank, the following information is shown:
As we can see, we have a timeline of our events. Although it is not very easy to understand these graphs, we can see that the screen’s construction took the longest in the timeline. Therefore, we can see that in summary (Summary tab), there are 513ms to process this frame 🤯.
I usually check the Bottom-up tab to understand what’s going on, so let’s take a look:
As you can see, we have a 345 ms delay in drawing the numbers (texts) on the screen without going into detail in the other processes. It is not normal behavior, as Flutter typically does this efficiently. So where could the problem be? We can apply a filter, hide all the elements related to the core libraries, and see if we find any issues in our developments.
We can identify how we see on the screen that the heaviest processes are related to our developments, in this case, in building the screen and drawing the components of the list. It indicates an error in our total product spending (219+219+ 73) ms. It means that of the 513ms, 511ms are being used to draw our developments 😂.
You can also enable the Performance Overlay from the developer tools, showing the consumption of the Raster and UI thread on the cell phone.
Let’s measure CPU consumption.
We can still use this example to analyze the following two tabs. Let’s start with the CPU profiler. Which will tell us how much computational wear is for our solution.
This functionality involves recording an execution period and will show us the results of the analysis:
We can see that no execution process of the high-performance tab is related to the implemented developments. Instead, the most comprehensive techniques in the low-performance screen are related to constructing the screen’s low performance.
Let’s measure memory consumption.
On the other hand, there is the memory tab (Memory). Let’s see what the analysis throws at us:
How we see the memory consumption rises when building the screen. Still, once it has finished, the consumption decreases. It occurs in the low-performance experience. But as we see in the high-performance screen, there is no evidence of a memory spike when drawing the experience.
It makes it more evident that we have problems at the time of construction of the low-performance screen.
Let’s measure internet consumption.
Well, let’s go to the internet data trip tab. This screen allows us to measure response times, data type, and responses, among other exciting elements:
Perfo experience Network: How you can see, this shows us all our consumption on the Internet. In case of making unnecessary requests or presenting high response times from some of the services, it will be evidenced in this report.
Headers on the left and the response on the correct information that may be relevant to our developments can be validated.
Let’s measure storage space consumption.
Before going to production, analyzing the size occupied by our applications is good. For this, we can use the available tools. In this case, the App Size tab.
App Size tab To analyze the size of your application, we can execute the following commands:
flutter build apk --analyze-size
flutter build appbundle --analyze-size
flutter build ios --analyze-size
flutter build linux --analyze-size
flutter build macos --analyze-size
flutter build windows --analyze-size
It will generate a JSON file. I recommend you always keep the last JSON generated because, as you will see shortly, you can compare between two analyses.
In this scenario, I generated two reports, one with an application with a video within its assets and the other without it. My idea is that this tab tells us where the increase in the space of our App occurred.
The image on the left, application without video vs. image on the right, App with video In the first analysis section, it allows us to see in detail what the storage space consumption of our application is. There is a notable difference between the two reports since one application weighs 54 MB and the other 68.3 MB, which indicates that something happened in the latest version that increased the weight of our App. But how do we validate that it was what occurred? For this, we will use the Diff tab:
The image on the left and the two reports are uploaded. On the right, you can see the resulting increase in the size of the application is a video located inside the images folder called video.mov 🤯.
This tool also allows us to see if we decrease the size of our App. Therefore, performing these analyses in our process before deployment to production is very useful.
How is performance improved?
At this time, there were already many elements that we analyzed. The only thing left to solve is the following: How do we fix our performance failures?
We must consider what we are doing in our experience and identify the best development practices to approach this type of solution, always looking at the official documentation.
For example, let’s think about the Low Perfo screen. We have a problem there. According to what was thrown in our analysis, the problem lies in the drawing of our net. But what are we drawing there? A long list of widgets.
The official documentation contains a section that tells us how we can work our lists in the best possible way (link)(link 2). If we follow the advice provided in these links, it will undoubtedly improve our performance.
Therefore, when we want to improve performance, we must consider what we are doing and check it against the official documentation.
I hope this article has been to your liking. Forgive the length a bit, but given the detail of the subject, it was necessary to cover several points. If you liked the article, remember to leave +50 👏 as a sign of gratitude.
I am convinced that with this tool, you will be able to identify the possible failures of your App and attack the problems that you see. But wait!! I did not tell you there is a way to measure some of these elements from automatic processes. Follow us to see the next part of this article, where I will tell you how we can do it 🧑🏻💻. I will also leave you the link to the example repository (repo link).