macOS Performance Comparison: Flutter Desktop vs. Electron
This article covers the following:
- The underlying engines and technologies powering Flutter and Electron.
- Important performance considerations and how they relate to Flutter Desktop and Electron.
- Real performance metrics demoing example Flutter Desktop and Electron applications on macOS.
Before reading on, keep in mind that this article isn’t intended to promote one solution over the other. The resources on modern computers and devices are enough that you would be hard-pressed to spot the difference between a well-made Flutter Desktop app compared to a well-made Electron app.
Performance is just one metric. Your team's developer productivity, experience, and knowledge are also important factors to consider when deciding which solution to use.
All of the code used for these tests are linked in the article; feel free to find mistakes or inconsistencies and help improve the quality of the results.
The Current State of Electron and Flutter Desktop
Electron has stood the test of time and proven itself to be a good solution to bring web applications to desktops. In fact, some of my favorite (and most used) desktop apps run on Electron, like Visual Studio Code, Figma, and Slack. Other popular apps include Discord, Skype, and Tusk.
Microsoft is moving Teams from Electron to Edge WebView2.
But for every good Electron app, there’s a bad one out there (no name dropping 👀). Electron applications are famous for their large executable size, heavy memory/resource usage, and, in some instances, long startup times and jank issues.
Flutter has the ambitious goal to target a variety of devices from a single codebase. Flutter has proven itself to be a good solution for mobile (Android and iOS) development, with decent performance that has steadily improved since its v1 release. There have been some performance issues over the years, but most of those have been addressed.
Flutter Desktop, however, is still in beta at the time of writing. There are no big-name applications made with Flutter Desktop that we can experiment with and directly compare to Electron. Time will tell how good of a solution it is in a production scenario, but from my personal experience, the initial performance results seem promising. A Flutter application that runs well on mobile should, in theory, run just as well, if not better, on desktop.
Let's discuss the languages and technologies driving Electron and Flutter.
It's also possible to interface with native code using native modules (see this article for an example).
Per the Dart Native docs:
During development, a fast developer cycle is critical for iteration. The Dart VM offers a just-in-time compiler (JIT) with incremental recompilation (enabling hot reload), live metrics collections (powering DevTools), and rich debugging support.
When apps are ready to be deployed to production — whether you’re publishing to an app store or deploying to a production backend — the Dart AOT compiler enables ahead-of-time compilation to native ARM or x64 machine code. Your AOT-compiled app launches with consistent, short startup time.
The AOT-compiled code runs inside an efficient Dart runtime that enforces the sound Dart type system and manages memory using fast object allocation and a generational garbage collector.
The key takeaway is that Dart can give you a fast developer experience in the form of hot reload, and the benefit of a small compiled application to the target architecture when you create a release build.
Here are some other noteworthy Dart features and updates:
- Dart recently added support for sound null safety, which enables the Dart AOT compiler to produce faster machine code (check out Dart and the performance benefit of sound types for more information).
- Dart’s foreign function interface (FFI) lets you use existing C libraries for better portability with the option of using highly tuned C code for performance-critical tasks. Read about Dart 2.13 for more information about Dart FFI.
Dart is growing up to be a great language in isolation, but its popularity can definitely be attributed to the rise of Flutter. Google and the Flutter team maintain Dart, meaning the team can optimize the framework (Flutter) and the language (Dart) to help improve the developer experience.
The following sections compare performance considerations between Flutter and Electron. These are areas that could have an impact on the performance of the final application, or your developer experience and the amount of effort needed to avoid potential performance problems.
Both the Flutter and Electron docs cover performance for each platform, which you should view for up-to-date information.
Application Size, Dependencies, and Modules
One important factor to consider is the executable size of your compiled application. Here Flutter is the clear winner. Flutter compiles to machine code, which means that Dart can be smart about what to include and what to strip from a final build. See this list of closed PRs related to AOT build sizes for Dart. This is an area of continuous improvement and measurement.
Electron applications, on the other hand, run on Chromium. Basically, every Electron application is bundled with its own browser. That’s the magic that makes Electron possible, but also why you get an executable around 150mb (or more) with a simple “Hello, World!” app. A similar Flutter application would be around 30mb. (Later on, in our performance metrics, we'll take a look at some real numbers to evaluate bundle size.)
Ignoring the sheer size of an Electron application, there are also other important factors to consider:
- With Electron, you need to be more mindful of what modules you include, as unused code will still be included in your final package (see the section on carelessly including modules).
- Loading modules in Electron is a surprisingly expensive operation. You as the developer need to take care to defer the loading of big modules to only when they're needed (see the section titled loading and running code too soon).
- Finally, it’s recommended that you bundle all of your application's code into a single bundle, as running
Dart, on the other hand, does a good job of stripping away unneeded code and dependencies. If you start a new Flutter project, then the build settings to run on macOS (along with all the other platforms) will already be set up and waiting.
Flutter requires a lot less configuration and you’ll immediately benefit from all the AOT compilation benefits that Dart provides when you build your application to the target platform.
Isolates, Web Workers, and Processes
Both Flutter and Electron apps can offload expensive work from the Main/UI thread, which is necessary when you want to perform operations that may potentially be UI blocking:
- In Dart, each thread is in its own isolate with its own memory, and the only way different isolates can communicate is by passing values through ports.
See Electron’s Blocking the main process and Flutter’s Isolates and event loops for more information about these processes.
While both frameworks can execute expensive work in a non-blocking way, it’s important to note that Electron spins up a couple of extra processes by default because it’s running Chromium.
Take a look at this screenshot from macOS Activity Monitor:
Activity Monitor showcasing an Electron "Hello, World" application on macOS.
You’ll notice three extra helper processes (Helper, GPU, Renderer), each of which takes up some memory and processing power:
- The main process creates and manages BrowserWindow instances and various application events.
- The render process runs the user interface of your app (a web page), which is an instance of webContents.
Check out Cameron Notes’s excellent article, Deep dive into Electron’s main and renderer processes. This is a great place to start if you want more information.
Flutter, on the other hand, only runs a single process, and you have the option to spawn extra isolates to offload work from the main thread.
Activity Monitor showcasing an Electron "Hello, World" application on macOS.
For more information on Flutter isolates, check out the following articles:
- Dart is indeed multi-threaded
- Featherweight Isolates in Flutter (a video that discusses how to use isolate groups to reduce a Flutter app’s memory footprint)
If you care to learn more, here are some good resources:
Both Flutter and Chromium use Skia, a 2D graphics library, to handle rendering.
From the MDN Web Docs:
This is incredibly exciting! See the following articles to learn more about Wasm:
- WebAssembly cut Figma’s load time by 3x
- Web support for Flutter
- Flutter web using WASM instead of dart2js
- Experimenting with Dart and Wasm
Flutter and Chrome DevTools
Improving your application's performance is not a simple task, and sometimes finding performance bugs can be like finding a needle in a haystack. For this, you need the right tools to assist you. Luckily, both Flutter and Electron have access to fantastic DevTools.
For this category, Electron has the upper hand. Chrome DevTools is an advanced piece of software that has been in development for many years. The performance profiling, debugging, network monitoring, layout inspection, and everything else are all phenomenal.
Dart DevTools, while amazing, is still young. I personally love using it and it provides you with everything you need to debug a Flutter application, but it definitely still has room for improvement.
Now that we've discussed important performance factors for each platform, let's run some applications and see how Flutter Desktop and Electron compare.
These tests were only performed on macOS Big Sur v11.5.2, on a MacBook Pro (16-inch, 2019), with the following specs:
- 2.3 GHz 8-Core Intel Core i9
- AMD Radeon Pro 5500M 4 GB
- Intel UHD Graphics 630 1536 MB
Source code and installation instructions are available in the Github repository. You’re encouraged to run these tests for yourself and do your own experiments.
Please note that these are just demos.
You can optimize these examples in many different ways for both Flutter and Electron. This is meant to be an out-of-the-box performance comparison; what you get with minimal developer effort.
Additionally, running some of these performance tests resulted in profiling overhead. Real-world results may look different when not profiling the application.
You should also expect some variance between different runs. Ideally, you should run these tests multiple times and take the average (this was not done for this article; only the first run was selected).
⚠️ In these examples, Electron Forge was used to configure and run the Electron projects. NPM version 7.23.0 was used.
⚠️ Flutter version 2.5.1 was used on the stable channel.
⚠️ Note that Chromium limits the number of Frames Per Second (fps). See Chrome’s Unlimited frame rate article for more information.
“Hello World” App!
A simple application that displays "Hello, World!"
App Startup Time
Both Flutter and Electron open and paint to the screen in under one second, with Flutter being slightly faster.
"Hello, World!" app, Flutter vs Electron startup time
Here we can see that even for a simple application, an Electron app takes up a lot of space!
- Deployment Target 10.11 and above: 37.3mb
- Deployment Target 11.0 and above: 22.7mb
Flutter uses the least amount of memory (~38mb compared to ~100mb). All those extra Electron processes add up.
"Hello, World" Flutter app. Memory ~37.9mb
"Hello, World" Electron app. Memory: ~99.6mb (44.9 + 26.1 + 18 + 10.6)
CPU, GPU, and Energy impact are not shown in this example.
Profiling / Frames Per Second
A silly example, but included for fun. Here the application window is resized to trigger a re-render.
As expected, both Flutter and Electron run comfortably at 60fps . Please note, though, that Chromium limits the number of fps. That's why the time per frame is always around 16ms for Electron.
Under 3ms per frame, 60fps.
Flutter, 60fps, with the average frame under 3ms
Around 16-17ms per frame, 60 fps.
Electron, 60fps, with the average frame time set to 16-17ms
Lottie Animations App
This example loads 150 Lottie animations and animates them all at the same time.
App Startup Time
Here, the Electron app opens half a second earlier than the Flutter app; however, the first content-full paint is at the same time. It should be noted that the Electron app has very noticeable frame drops for the first few seconds.
"Lottie Animations" app, Flutter vs Electron startup time
Here, loading extra modules could result in a significant increase in bundle size when using Electron (~80mb bigger compared to the "Hello, World" sample).
The Flutter app only increases by ~7mb after adding the Lottie package and animation files.
- Deployment Target 10.11 and above: 40.7mb
- Deployment Target 11.0 and above: 29.1mb
Flutter uses fewer resources in all the categories: CPU, GPU, Memory, and Energy.
The stand-out result, however, is the massive amount of memory used in the Electron version. Roughly ~2.2gb compared to Flutter at ~170mb.
Flutter has a lower CPU and GPU usage: ~130% (CPU) and ~13% (GPU), compared to Electron’s ~215% (CPU) and 41% (GPU).
"Lottie" Flutter app. CPU: ~130.6% and GPU: ~12.8%
"Lottie" Electron app. CPU: ~215% (0.2 + 101.6 + 113.4) and GPU: ~41%
Flutter has a much lower memory usage at ~170mb compared to Electron at ~2.2gb.
"Lottie" Flutter app. Memory: ~168.5 MB
"Lottie" Electron app. Memory: ~2261.3mb (2100 + 95.6 + 55.3 + 10.4)
Flutter uses less energy, with an impact of ~130 compared to Electron at ~220.
"Lottie" Flutter app. Energy: ~131.6
"Lottie" Electron app. Energy: ~220.7 (0.2 + 137.8 + 82.7)
Profiling / Frames Per Second
In this example, there’s a noticeable runtime difference between Flutter and Electron. On startup, Flutter runs immediately, and maintains a steady 40-45fps, while Electron shows significant frame drops for the first few seconds and then maintains 20-25fps.
Ranging from 10–30ms per frame, with an average of approximately 40–45fps.
"Lottie Animations" app. Flutter Desktop profiling.
Ranging from 25–60ms per frame, with an average of approximately 20fps–25fps.
"Lottie Animations" app. Electron profiling.
High-Resolution Images App
This example loads 100 high-resolution images over the network, caches them, scales them down to a small size, and continuously rotates the images.
Note that on Flutter, the cached_network_image package is used to cache the images between runs.
App Startup Time
Both Flutter and Electron open in under one second, with the Flutter app opening and loading the images slightly faster. There are noticeable frame drops in the Electron app for the first few seconds.
"Rotating Images" app, Flutter Desktop vs Electron startup time.
The Electron app is the exact same size as the "Hello, world" example. The Flutter app is 5mb bigger, after including the image caching package.
- Deployment Target 11.0 and above: 27.7mb
The results are a lot closer in this example, except for memory usage, where Flutter uses approximately 100mb less.
Not a big difference between Flutter and Electron. Flutter using slightly fewer resources.
"Images" app Flutter. CPU: ~13.6% and GPU: ~2.97%
"Images" app Electron. CPU: ~17.5 % (1.1 + 7.7 + 8.7) and GPU ~2.8%
Flutter using approximately 100mb less than Electron.
"Images" app Flutter. Memory : ~73.5mb.
"Images" app Electron. Memory: ~173.5mb (74.2 + 54.4 + 29.1 + 15.8)
Not a big difference between Flutter and Electron, between 12–17% energy impact.
"Images" app Flutter. Energy Impact: ~12.5
"Images" app Electron. Energy Impact: ~17 (1.1 + 8.4 + 7.5)
Profiling / Frames Per Second
There are noticeable frame drops in the Electron version on App start. But after the first few seconds, both run at 60fps without issue.
Aside from the occasional frame drop, it renders close to 1ms per frame. An easy 60fps.
"Images" app Flutter. 1ms per frame, 60fps.
Forced 16ms per frame, 60fps.
"Images" app Electron. Frames set to 16ms, 60fps. Noticeable frame drops on app start, however.
You have to make your own conclusion from the data presented 🙂. Both are excellent cross-platform choices if you want to bring your application to desktop.
A summary of the findings is provided below:
- As an Electron app grows, it may struggle with startup time and time to first contentful paint. However, apps like VSCode and Figma prove that this can be avoided.
- Electron is proven to be a working solution; if you can write a fast and performant web application, then you can do it on Electron as well.
- Has a large installation size and memory usage.
- You need to be more mindful of the modules you include, when they’re loaded, and how your application is packaged.
- Wasm integration can drastically improve certain tasks.
- Flutter Desktop is still in Beta and unproven on a larger scale.
- Flutter will have a smaller memory footprint and install size. These numbers are also likely to improve, especially as Dart continues to be optimized.
- Targeting multiple platforms is easy with minimal developer configuration needed.
- Flutter's platform integration and FFI are fantastic solutions.
- Dart's null safety and its potential integration with Wasm are both icing on the Flutter cake.
For the performance experiments, it would’ve been fantastic to demo two big, and similar, Flutter and Electron applications side-by-side and to be able to compare more real-life scenarios.
This will be an interesting conversation once production Flutter desktop apps start surfacing.
- Publishing Android libraries to MavenCentral in 2021
- Flutter vs React Native: The Ultimate Comparison
- Keeping Public API in Check With the Kotlin Binary Validator Plugin
- Activity Feed Personalization 101: Top Feed Features to Improve User Engagement
- Swift WebSockets: Starscream or URLSession in 2021?
- React Native: How To Build Bidirectional Infinite Scroll