You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
On app startup, when the work to start the engine, launch the app, and set the Dart code running is serialized on the platform thread, the time to first interactable frame takes longer. This issue tracks investigating and mitigating the regression.
These were taken on a Pixel 6a. The Flutter app process in the traces is called com.example.bug_163064.
I added events to show two phases of app startup:
engineStartup
covers the interval from FlutterLoader.startInitialization through the engine's invocation of the Dart main function
firstFrameRender
the interval in the app's Dart code from the start of main through the completion of WidgetsBinding.waitUntilFirstFrameRasterized
WidgetsBinding.waitUntilFirstFrameRasterized completes when the rasterizer finishes rendering the first frame on screen. Some apps that are reporting latency regressions are using that event to compute their startup metrics.
In these traces, there is a small difference in the overall time from the beginning of engineStartup to the end of firstFrameRender. (176ms for merged threads versus 161ms for separate UI thread).
For this minimal app the Dart ReadProgramSnapshot work does not consume much of this time (about 4ms).
However, note the interleaving of Android host platform tasks and Dart execution on the merged UI/platform thread.
In the separate UI thread trace, the firstFrameRender phase begins immediately after the end of engineStartup.
But in the merged thread trace, there is a gap of 30ms before firstFrameRender begins. The platform thread is doing various Android-related work (some of which does not seem to be specific to Flutter) before it can execute the Dart message handling tasks.
There is more latency while the platform thread continues to be split between Android work (such as layout of the Android activity) and Dart execution work until the first frame is rendered. With a separate UI thread the Android and Dart tasks can run concurrently.
Initializing the engine on a separate thread and then shifting it to the platform thread will require changing many threading design assumptions.
I tried a quick experiment of running an engine with --no-enable-merged-platform-ui-thread and calling MessageLoopTaskQueues::Merge for the platform and UI task runners at the end of Engine::Run.
The first issue I saw was an assertion failure in an fml::WeakPtr captured in a lambda that was posted to the UI task runner before the merge and then used on the platform thread after the merge. (fml::WeakPtr can only be used on the thread where it was created)
After that Dart_EnterIsolate failed a similar assertion which verifies that the isolate is running on its owner thread.
Given that we can't move the Android work to another thread, can you try and figure out how complicated it would be to actually move the engine from one thread to the other after initialization? Is it a week, month, or quarter of work?
The internal customer is ready to proceed with performance with the --merged-platform-ui-thread=mergeAfterLaunch flag, and the ability to opt-out can be removed. I'll close this issue as fixed.
This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.
Activity
zanderso commentedon Feb 11, 2025
cc @jtmcdole @jason-simmons @a-siva
jtmcdole commentedon Mar 12, 2025
@jason-simmons - can you add some before and after timing captures for the default flutter counter app?
jason-simmons commentedon Mar 12, 2025
Uploaded traces of the default counter app:
These were taken on a Pixel 6a. The Flutter app process in the traces is called
com.example.bug_163064.I added events to show two phases of app startup:
engineStartupFlutterLoader.startInitializationthrough the engine's invocation of the DartmainfunctionfirstFrameRendermainthrough the completion ofWidgetsBinding.waitUntilFirstFrameRasterizedWidgetsBinding.waitUntilFirstFrameRasterizedcompletes when the rasterizer finishes rendering the first frame on screen. Some apps that are reporting latency regressions are using that event to compute their startup metrics.In these traces, there is a small difference in the overall time from the beginning of
engineStartupto the end offirstFrameRender. (176ms for merged threads versus 161ms for separate UI thread).For this minimal app the Dart
ReadProgramSnapshotwork does not consume much of this time (about 4ms).However, note the interleaving of Android host platform tasks and Dart execution on the merged UI/platform thread.
In the separate UI thread trace, the
firstFrameRenderphase begins immediately after the end ofengineStartup.But in the merged thread trace, there is a gap of 30ms before
firstFrameRenderbegins. The platform thread is doing various Android-related work (some of which does not seem to be specific to Flutter) before it can execute the Dart message handling tasks.There is more latency while the platform thread continues to be split between Android work (such as layout of the Android activity) and Dart execution work until the first frame is rendered. With a separate UI thread the Android and Dart tasks can run concurrently.
johnmccutchan commentedon Mar 31, 2025
Have we considered keeping the separate threads during startup and then merging them?
jason-simmons commentedon Apr 1, 2025
Initializing the engine on a separate thread and then shifting it to the platform thread will require changing many threading design assumptions.
I tried a quick experiment of running an engine with
--no-enable-merged-platform-ui-threadand callingMessageLoopTaskQueues::Mergefor the platform and UI task runners at the end ofEngine::Run.The first issue I saw was an assertion failure in an
fml::WeakPtrcaptured in a lambda that was posted to the UI task runner before the merge and then used on the platform thread after the merge. (fml::WeakPtrcan only be used on the thread where it was created)After that
Dart_EnterIsolatefailed a similar assertion which verifies that the isolate is running on its owner thread.johnmccutchan commentedon Apr 1, 2025
Given that we can't move the Android work to another thread, can you try and figure out how complicated it would be to actually move the engine from one thread to the other after initialization? Is it a week, month, or quarter of work?
Reduce app startup latency by initializing the engine on a separate t…
Reduce app startup latency by initializing the engine on a separate t…
Reduce app startup latency by initializing the engine on a separate t…
Reduce app startup latency by initializing the engine on a separate t…
Reduce app startup latency by initializing the engine on a separate t…
Reduce app startup latency by initializing the engine on a separate t…
Reduce app startup latency by initializing the engine on a separate t…
zanderso commentedon Aug 18, 2025
The internal customer is ready to proceed with performance with the
--merged-platform-ui-thread=mergeAfterLaunchflag, and the ability to opt-out can be removed. I'll close this issue as fixed.github-actions commentedon Sep 1, 2025
This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of
flutter doctor -vand a minimal reproduction of the issue.