Patch Performance
Shorebird uses its own fork of Flutter.
Shorebird’s fork works exactly as Google’s does, including passing all the same functionality and performance tests.
Using Shorebird’s fork should not result in any change in your app. If you ever see any change, reach out on Discord so the Shorebird team can help diagnose.
Patching
Section titled “Patching”Updating a Flutter app in production is unique to Shorebird’s fork.
When using Shorebird your app has two different modes of running, un-patched (“release”) and patched (“patch”). Both release and patch builds of your app should function identically as they would without Shorebird. The only caveat is on iOS where patched builds can sometimes run slower than release builds due to iOS restrictions.
The iOS App Store requires all app update systems (like Shorebird’s) to use an interpreter. An interpreter is a software program that can run other programs rather than running the program directly on hardware. Interpreters are slow.
To work around the performance limitations of an interpreter, Shorebird’s updating system is designed to avoid using the interpreter as much as possible. When you make a “patch” with Shorebird, Shorebird examines the change and attempts to set up the “patch” to only run the new or changed code within the interpreter (slow), and thus leave all un-changed code running on the CPU (fast).
Why iOS patches are sometimes slow
Section titled “Why iOS patches are sometimes slow”Most of the time when you make a change to a part of your program, only that part of the program is changed. However sometimes small changes to one part of the program can produce large changes in (seemingly) unrelated parts.
This is due to how compilers (programs that turn source code into machine code) optimize their output. Dart’s compiler uses several tricks to optimize programs including “type flow analysis” and “inlining”, both of which can cause these unexpected non-local changes.
As an example, imagine you have a part of your program:
And you change it to add:
In that example, isEven and foo didn’t change. So you would expect Shorebird
should be able to run both of those on the CPU (not have to use an interpreter).
However this is unfortunately not the case. In the “before” case, because isEven
was never called with nullable values Dart optimized away all of the null checks
in the compiled isEven code. (That’s a good thing.) Unfortunately that means
that when the new code now used isEven in a nullable context the compiled
contents of “isEven” change, to include null checks. Since the contents of
isEven changed, Shorebird will conservatively assume that the behavior of all
callers of isEven might have changed as well and won’t use the CPU versions of
those either. Thus if your change (however small) might happen to have caused
the Dart compiler to reconsider optimizations for some other critical section of
code, tiny changes can sometimes cause large sections of your application to
“un-link” and run in the interpreter.
Avoiding performance changes in iOS patches
Section titled “Avoiding performance changes in iOS patches”Unfortunately there is currently no good way to predict if a patch will cause a performance change to your application.
The good news is that this behavior is relatively rare. 9 out of 10 patches do
not run into unexpected non-local changes like this and exhibit no difference
after patching. Furthermore, when performance impacting changes occur,
shorebird patch warns you. The bad news is that predicting when a change will
trigger the Dart compiler to cause non-local changes to your program is very
difficult.
When these non-local changes occur, the best recommendation at this time is to try to make a new, smaller diff, and patch again.
For the vast majority of patches there is no performance difference at all. For those which do see a difference, sometimes that difference may still be worth the trade-off as a stop-gap between now and when a user can get an update via the App Store.
Several approaches exist to remove this limitation on iOS, and Shorebird intends to continue improving this behavior in the future.
You can’t predict it from the change, but you can measure it once the patch is built: check its link percentage in CI, as described in Link percentage and slow patches.
Link percentage and slow patches
Section titled “Link percentage and slow patches”Read this if shorebird patch ios or shorebird patch ios-framework warned
that it could only share part of your Dart code with the released app, refused
to publish because the link percentage was below the minimum, or your app runs
slower after a patch.
What link percentage means
Section titled “What link percentage means”When Shorebird builds an iOS patch, it links the patch against the release. Code the linker can match to the release keeps running on the CPU. Changed code, and code that depends on it, runs in the interpreter. The link percentage is the share of the patch’s Dart code that was linked this way.
A patch that changes a few functions usually links at close to 100%. Below 90% is worth investigating. The usual causes are a patch that contains more change than you meant to ship, or one built differently from the release. Sometimes a small change unlinks a lot of code on its own, as described in Why iOS patches are sometimes slow. A low percentage only slows your app down if the unlinked code is code your app spends its time in.
Link percentage applies to shorebird patch ios and
shorebird patch ios-framework. Android, macOS, Linux and Windows patches are
not linked and have none.
The warning below 90%
Section titled “The warning below 90%”When the link percentage is below 90%, shorebird patch warns:
The patch summary also names the debug file:
The patch still publishes. Check the common causes below. If none applies, publish to a staging track first and check the app’s performance before promoting the patch.
The failure below 20%
Section titled “The failure below 20%”In 1.6.135 and newer, shorebird patch refuses to publish a patch that links
below 20%:
The check also runs with --dry-run, so a dry run fails the same way. Patches
this far below normal usually come from one of the
common causes below, so look there before lowering the
threshold. To publish the patch anyway, pass a lower --min-link-percentage.
--min-link-percentage=0 accepts any link percentage, as older CLI versions
did.
Check the link percentage in CI
Section titled “Check the link percentage in CI”--min-link-percentage also works the other way: raise it to stop a patch that
links worse than you expect before it reaches users.
To check a patch without publishing it, for example on every pull request, add
--dry-run. In 1.6.134 and older, a dry run exits before the link percentage is
checked, so this needs 1.6.135 or newer:
See shorebird patch ios --help for the other options.
Common causes
Section titled “Common causes”- The patch contains more change than the fix. Upgraded dependencies
(
pubspec.lock), regenerated code and refactors all change compiled code outside the fix. Build the patch from the release’s commit plus the fix. Even a small change can unlink code that calls it, as described in Why iOS patches are sometimes slow. - The patch is built with different settings from the release. Pass the same
--flavor,--targetand--dart-define(or--dart-define-from-file) values you released with. Shorebird builds the patch with the release’s Flutter version and obfuscation setting for you, except in the case below. - No
--release-versionin CI, on 1.6.136 and older. Whenshorebird patchhas no--release-versionand can’t prompt, it builds the app to find the release version and can reuse that build as the patch. That build doesn’t apply the release’s obfuscation, so a patch for a release built with--obfuscatelinks poorly. Always pass--release-versionin CI. - Two builds at once in the same project, on 1.6.134 and older. Running
shorebird patch ioswhile another build, such asshorebird patch android, runs in the same project could build the iOS patch from the other build’s Dart code, which links very poorly. Update the CLI withshorebird upgrade, or run the builds one after the other.
Getting help
Section titled “Getting help”If none of these explain a low link percentage, send Shorebird
build/patch-debug.zip with your app ID and release version. It contains your
compiled Dart code, so read
Sending a patch-debug.zip to Shorebird
for what is inside and how to send it privately.