-
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
Optimize search for the default bundle #39975
Conversation
5fe9870
to
f08f634
Compare
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.
When tested internally, this improves startup of
customer: chalk
by 30% - see http://shortn/_WcifhO48X6.
Wow, that's significant!
-
Does macOS have the same problem?
_dartBundle = bundle ?: [NSBundle bundleWithIdentifier:kAppBundleIdentifier]; -
Can you reuse this in other places in
FlutterDartProject
, see for example
engine/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm
Lines 111 to 112 in 706fd9c
NSString* applicationFrameworkPath = [mainBundle pathForResource:@"Frameworks/App.framework" ofType:@""];
bundle = [NSBundle bundleWithIdentifier:[FlutterDartProject defaultBundleIdentifier]]; -
Testing
As this is a pure performance change, no tests are added.
I don't think this is strictly true since it's adding lines of code that sure could have a bug in them, causing it to always incorrectly fall back to the slow implementation...
You could make FLTBundleWithIdentifier
an extern
in FlutterDartProject_Internal.h
and test it explicitly. For example, you could create all the files needed to create a NSBundle
in the test, pass the URL hint in, and then make sure that one is selected.
// Callers can provide a `hintURL` for where the bundle is expected to be | ||
// located in. If the desired bundle cannot be found here, the implementation | ||
// falls back to `+[NSBundle bundleWithIdentifier:]`. | ||
static NSBundle* FLTBundleWithIdentifier(NSString* bundleID, NSURL* hintURL) { |
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 don't think this needs to be static, you can nudge the compiler to inline with NS_INLINE
.
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!
Also regarding the general review comment:
You could make
FLTBundleWithIdentifier
anextern
inFlutterDartProject_Internal.h
and test it explicitly.
This is pretty new to me, but it does seem like using an extern
is incompatible with NS_INLINE
, or static
methods in general. I couldn't make FLTFrameworkBundleInternal
static / extern, if we want to export it in FlutterDartProject_Internal.h
. Please let me know if you have any ideas or if this is ok.
// located in. If the desired bundle cannot be found here, the implementation | ||
// falls back to `+[NSBundle bundleWithIdentifier:]`. | ||
static NSBundle* FLTBundleWithIdentifier(NSString* bundleID, NSURL* hintURL) { | ||
NSArray<NSURL*>* candidates = [[NSFileManager defaultManager] |
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.
Instead of contentsOfDirectoryAtURL
, which loads all the contents of a directory into an array even if you only were looking for the first one, there's a NSDirectoryEnumerator
which will walk the tree as you enumerate over it (maybe overkill for this use-case with a small number of files, but since you're optimizing), you can tell it to skip hidden files, do a shallow traversal, etc:
NS_INLINE NSBundle* FLTBundleWithIdentifier(NSString* bundleID, NSURL* hintURL) {
NSDirectoryEnumerator<NSURL *> *frameworkEnumerator = [NSFileManager.defaultManager
enumeratorAtURL:hintURL
includingPropertiesForKeys:nil
options:NSDirectoryEnumerationSkipsSubdirectoryDescendants|NSDirectoryEnumerationSkipsHiddenFiles
// Not interested in the error as there is a fallback.
errorHandler:nil];
for (NSURL* candidate in frameworkEnumerator) {
NSBundle* bundle = [NSBundle bundleWithURL:candidate];
if ([bundle.bundleIdentifier isEqualToString:bundleID]) {
return bundle;
}
}
// Fallback to slow implementation.
return [NSBundle bundleWithIdentifier:bundleID];
}
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 didn't know about this, thanks for the suggestion, done!
bundle = [NSBundle bundleWithIdentifier:[FlutterDartProject defaultBundleIdentifier]]; | ||
// The default build system for Flutter places the default bundle in the | ||
// same directory as the engine bundle (as they are both frameworks). | ||
NSURL* defaultBundleHintURL = [engineBundle.bundleURL URLByDeletingLastPathComponent]; |
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.
minor nit, URLByDeletingLastPathComponent
is a property, dot notation:
NSURL* defaultBundleHintURL = [engineBundle.bundleURL URLByDeletingLastPathComponent]; | |
NSURL* defaultBundleHintURL = engineBundle.bundleURL.URLByDeletingLastPathComponent; |
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.
This is obsolete - see the next comment below
// same directory as the engine bundle (as they are both frameworks). | ||
NSURL* defaultBundleHintURL = [engineBundle.bundleURL URLByDeletingLastPathComponent]; | ||
bundle = | ||
FLTBundleWithIdentifier([FlutterDartProject defaultBundleIdentifier], defaultBundleHintURL); |
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.
If this is still nil
you could call it again with the hint NSBundle.mainBundle.privateFrameworksURL
(the main bundle Frameworks
directory), not sure if that case is actually likely though.
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.
Using this is actually much better and keeps the code simpler. It turns out that NSBundle.mainBundle.privateFrameworksURL
resolves to the same thing as engineBundle.bundleURL.URLByDeletingLastPathComponent
. As such, I removed the need to pass a hint and renamed FLTBundleWithIdentifier
to FLTFrameworkBundleWithIdentifier
and always search within this path.
Thank you for the in-depth review!
I don't have a setup for profiling macOS apps now so I can't say for sure now, but I would expect this to also happen there – we should perhaps follow up separately. It seems like Chrome also encountered this problem on macOS - see
We don't have the source to
This is the last remaining reference to
I found it too hard to create a |
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.
LGTM, thanks for tracking this weird perf issue down.
Filed flutter/flutter#122028 to track sharing the optimization on macOS.
+[NSBundle bundleWithIdentifier]
is slow, and scales to the size of the app. This call during startup significantly affects the startup ofcustomer: chalk
.This implementation searches within the
privateFrameworksURL
where the default bundle is expected to be located. When tested internally, this improves startup ofcustomer: chalk
by 30% - see http://shortn/_WcifhO48X6.It also falls back to
+[NSBundle bundleWithIdentifier]
so there is no functional change in behavior for customers that may be using alternate build systems.List which issues are fixed by this PR. You must list at least one issue.
Fix flutter/flutter#37826
If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.
Pre-launch Checklist
///
).If you need help, consider asking for advice on the #hackers-new channel on Discord.