Patch Performance
Shorebird uses its own fork of Flutter.
Our 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, please let us know so we can work with you to 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 to 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:
bool isEven<T extends int?>(T value) => value?.isEven ?? false;
void foo(int x) { print(isEven(x));}
And you change it to add:
bool isEven<T extends int?>(T value) => value?.isEven ?? false;
void foo(int x) { print(isEven(x));}
void bar(int? x) { print(isEven(x)); }
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 we will not 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’s tooling and console will warn 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 we have 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.
We have several approaches to explore to remove this limitation on iOS and intend to continue to improve this behavior in the future.