-
Notifications
You must be signed in to change notification settings - Fork 6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Setup default font manager after engine created, to improve startup performance #18225
Setup default font manager after engine created, to improve startup performance #18225
Conversation
This is creating a race between the task that sets up the font manager and users of the engine that assume that the engine is fully initialized after it is created. Most of the engine initialization work is not done on the Android main thread. If the app is blocking the main thread during the call to |
Cross reference #18047 and #17192. Those PRs also aim at speeding up startup time by making some work asynchronous. Most parts of shell and engine are initialized on other threads (e.g., UI thread), but it seems that the platform thread (Android's main thread) is currently blocked until all those initializations are finished (see, e.g., the Line 167 in 9d8daf2
@jason-simmons : do we have a benchmark that verifies |
But if the concern is the latency of The default font manager provided by If we can't make shell creation async, then another potential workaround would be calling |
Since |
One more thing, I suppose that the first call to |
@jason-simmons @liyuqian : Looking forward to your further comments. |
I've made a update. |
0d297ba
to
9788976
Compare
Just chatted with @jason-simmons offline. We think "there might be a race between the deferred task that calls Running BTW, how important is this improvement to your app, @scutlight? Is it "customer: critical" or "customer: blocker"? CC @zoeyfan for evaluating the importance from the customer's perspective. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Discussed this with @chinmaygarde. The approach of deferring FontCollection::SetupDefaultFontManager
to a task queued to the UI thread should be safe. The FontCollection
is only accessed on the UI thread and the setup task will run before any other UI thread tasks that might use fonts.
One potential concern is that the background thread that calls SkFontMgr::RefDefault
may not have completed when the UI thread task tries to access the RefDefault
singleton. If that happens, then the UI thread will busy wait until the singleton is ready due to the implementation of Skia's SkOnce
(see https://github.com/google/skia/blob/master/include/private/SkOnce.h#L44)
The PR does minimize the chance of that happening by starting the RefDefault
immediately after libflutter.so
is loaded.
shell/common/engine.h
Outdated
//---------------------------------------------------------------------------- | ||
/// @brief Setup default font manager according to specific platform. | ||
/// | ||
/// @attention This operation calls `SkFontMgr::RefDefault` which is |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove the @attention
comment. The call to SkFontMgr::RefDefault
is an implementation detail that will vary across platforms.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
* singleton owned by Skia. Note that, the first call to SkFontMgr::RefDefault() will take | ||
* noticeable time, but later calls will return a reference to the preexisting font manager. | ||
*/ | ||
public static native void nativeCreateDefaultFontManager(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change this to nativePrefetchDefaultFontManager
(and do the same for the CreateDefaultFontManager
function in flutter_main.cc
).
I'd prefer to avoid the name Create
given that the function does not return a font manager instance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
@@ -144,6 +144,17 @@ public InitResult call() { | |||
|
|||
System.loadLibrary("flutter"); | |||
|
|||
// Pre-warm the default font manager as soon as possible on a background thread. | |||
// It helps to reduce time cost of engine setup that blocks the platform thread. | |||
new Thread( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use Executors.newSingleThreadExecutor().submit()
for consistency with other threads used in FlutterLoader
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
@@ -155,6 +156,10 @@ void FlutterMain::SetupObservatoryUriCallback(JNIEnv* env) { | |||
}); | |||
} | |||
|
|||
static void CreateDefaultFontManager(JNIEnv* env, jclass jcaller) { | |||
sk_sp<SkFontMgr> font_mgr(SkFontMgr::RefDefault()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove the sk_sp<SkFontMgr>
and just call SkFontMgr::RefDefault()
. Add a comment explaining that calling SkFontMgr::RefDefault
will initialize a singleton owned by Skia.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
9788976
to
7f7358e
Compare
It is clear that "the setup task will run before any other UI thread tasks that might use fonts", refer to Jason's latest #18225 (review), and my previous #18225 (comment)
Sorry for confusing screenshot. It is parallelized. The UI thread is busy waiting until the singleton is ready due to the implementation of Skia's
Yeah, I agree that. Maybe we can put this
Maybe not so emergent as critical, but really important since the whole engine startup is non-trivial and blocking the Android's main thread. |
Thanks @scutlight ! It seems that the data race is no longer a problem. BTW I verified locally that our
@xster @gaaclarke : the current PR only speeds up Android startup time. You might be interested in extending it to iOS as well. |
7f7358e
to
e5b6447
Compare
fml::TaskRunner::RunNowOrPostTask(task_runners_.GetUITaskRunner(), | ||
[engine = weak_engine_] { | ||
if (engine) { | ||
engine->SetupDefaultFontManager(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What protection do we have that someone won't use FontCollection.collection_
before this executes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The FontCollection
is only usable on the UI thread, and UI thread tasks are executed in the order that they were queued. Therefore the task that completes setup of the FontCollection
will execute before any task that might query the FontCollection
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There could already be something in the UI event queue that will use the FontCollection though.
platform_thread->execute({
ui_thread->execute({ shell->setup(); });
ui_thread->execute({ someFontCollectionUsingFunction(); });
});
There is no guarantee in this case that SetupDefaultFontManager
will execute before someFontCollectionUsingFunction
.
edit: If this is how things work this should be safe. All usages of the font collection on the ui thread are given a reference to a shell that has already been setup. This might be the case.
platform_thread->execute({
shell->setup();
ui_thread->execute({ someFontCollectionUsingFunction(shell); });
});
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The embedder APIs will not return a shell to the caller until Shell::Setup
has completed and the SetupDefaultFontManager
task has already been queued.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just want to make sure my question is answered before merge. It isn't obvious the protections we are doing here and I don't want to introduce a subtle race condition.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Talked with @jason-simmons offline, while the thread-safety isn't asserted at compile-time he assures me that it is practically impossible to have something else in the UI event queue because of the way the embedder API works. I asked for a runtime assert if one was easy to insert. I'll leave that up to his judgement.
@jason-simmons @gaaclarke : hi, since both of you have approved, shall this pr be merged? |
Sorry for the delay - the pipeline that rolls the engine into the Flutter framework has been having problems. I'm planning to merge this PR when the roller stabilizes. |
…tartup performance (flutter/engine#18225)
BTW, there's also a big speedup https://flutter-perf.skia.org/e/?begin=1590623523&end=1590741783&keys=X0fffad7016a0440022353623dc52ae28&xbaroffset=17822 |
## Description As the related issue refer, the application may be doing too much work on its main thread even in a simple hello_world demo. That is because the creation of `Engine` on the ui thread takes a noticeable time, and it is blocking the platform thread in order to run `Shell::Setup` synchronously. The cost of `Engine`'s constructor is mainly about the creating of root isolate. Actually, there used to be another time-consuming process, the default font manager setup, which was resolved by #18225. Similar to #18225, this pr move the creation of root isolate out from creating `Engine`. After this action, the main thread blocking is quite an acceptable slice. ## Related Issues flutter/flutter#40563 could be resolved by this pr.
Shell::Create
may gain 25~50% performance improvement by this pr ^_^SkFontMgr::RefDefault
is time-consuming, about 15ms in Pixel2, which is blocking Android's main thread during engine initialization. It is called byFontCollection
while creatingEngine
, and I think we can just make that happen right after initialization instead of initializing.before this pr:
after this pr:
Shown as the above figures,
Shell::Create ~60ms ==> ~30ms
ShellSetupUISubsystem ~25ms ==> ~10ms