Back in November, I shared a summary of some of the work that I've been
doing on reducing the size of release builds generated by the Royale
compiler. Essentially, I've been trying to ensure that we could take better
advantage of Closure compiler's advanced optimizations for minification,
which includes removing dead code and renaming symbols so that they have
shorter names.

Since then, I've made some more improvements to the Royale compiler, which
will allow even more optimization. I figured that it is a good time to
share my latest results.

To fully understand the context of today's post, I recommend reviewing the
introduction that I wrote for my earlier post (you can stop once I start
talking about the results). Here's the link to the archive:

https://lists.apache.org/thread.html/rf53d200969d795a15bda5d6635b904d89fa7f6af151df5dca15ccbbb%40%3Cdev.royale.apache.org%3E

Last time, in my final set of results, some public symbols were allowed to
be renamed in the release build, but some were not. If I tried to rename
all public symbols, it would cause parts of the apps to stop working
correctly. My work since then has been finding ways to ensure that more
public symbols can be renamed safely.

What I determined is that the way that we encode MXML data in JavaScript
results in a lot of dynamically setting properties using string names,
similar to `variable["propName"] = value`. Setting properties dynamically
is generally bad when minimizing with Closure compiler because it won't
understand that it needs to keep those properties, and it may remove them
as dead code (even though it's not dead code), or it may change their names
(while our code still uses the original names).

Luckly, our MXML data in JS is well-structured, so it's possible to walk
the JS tree when creating the release build and extract names that are used
dynamically in MXML. Then, we can pass those names to Closure compiler
manually. Basically, we're extending the Closure compiler with an extra
analysis step that has specialized knowledge of Royale. This allows us to
rename more public symbols because we can exclude the ones that we know
cannot be renamed).

I added a new compiler option, *prevent-rename-mxml-symbol-references*,
which defaults to true. You will probably never want to change this to
false, but the option is there, if anyone ever determines that it is needed
in some edge case.

Okay, now to the results. I built the same four app projects as last time.

I used the following commits, if anyone wants to try reproducing on their
own computer:

royale-compiler: 65805281ea898b3a0225922f5956c864f810c423
royale-asjs: fab5f5444e0e9ee7f8f59e5cf1d4b1144a31025e
spectrum-royale: acf4007fa322d61e1d0ae874e4c2009f8645654c

==========
Baseline: royale-compiler 0.9.7
==========

HelloWorld: 71 KB
ASDoc: 237 KB
TourDeJewel: 1125 KB
SpectrumBrowser: 944 KB

As with my previous post, I built all of the example projects with the
Royale 0.9.7 compiler first. However, I built them with the latest
framework code. This ensures that only the differences in the compiler are
being compared, and we don't have extra noise from differences in the
framework code.

-----

Now, let's see some results with the latest compiler.

All results below include the +/- difference in KB and %. These values are
always in comparison to the baseline numbers above.

==========
Result 1: Latest compiler with default options
==========

HelloWorld: 87 KB (+16 KB / +23%)
ASDoc: 261 KB (+24 KB / +10%)
TourDeJewel: 1159 KB (+34 KB / +3%)
SpectrumBrowser: 980 KB (+36 KB / +4%)

As with the previous post, the increase in file size here is expected. The
latest compiler fixes some bugs that were present in 0.9.7 that caused
certain symbols to be renamed/removed when they should not have been.

==========
Result 2: Disable symbol exports and allow non-public symbols to be renamed
==========

HelloWorld: 74 KB (+3 KB / +4%)
ASDoc: 226 KB (-11 KB / -5%)
TourDeJewel: 966 KB (-159 KB / -14%)
SpectrumBrowser: 897 KB (-47 KB / -5%)

Compiler options used:
*-export-public-symbols=false*
*-prevent-rename-protected-symbols=false*
*-prevent-rename-internal-symbols=false*

These results are consistent with a similar test from my previous post. I
included it to show that nothing has changed, and to have an intermediate
step between the default options and the full optimizations.

==========
Result 3: Allow public symbols to be renamed too
==========

HelloWorld: 66 KB (-5 KB / -7%)
ASDoc: N/A
TourDeJewel: N/A
SpectrumBrowser: 857 KB (-87 KB / -9%)

Compiler options used:
-export-public-symbols=false
-prevent-rename-protected-symbols=false
-prevent-rename-internal-symbols=false
*-prevent-rename-public-symbols=false*

This is a compiler option that I didn't include in the tests last time
because renaming all public symbols broke every app before. Now that the
Royale compiler walks the MXML data tree, it's possible to allow renaming
of public symbols in some apps (but not all, for reasons I will explain). *This
combination of compiler options provides the largest possible file size
savings in release builds.*

You'll notice that ASDoc and TourDeJewel are marked N/A. This is because
they use the ConstantBinding class, which sets up bindings dynamically.

<js:ConstantBinding sourceID="listModel" sourcePropertyName="iconListData"
destinationPropertyName="dataProvider"/>

When compiled to JS, this code basically converts to this:

this[binding.destinationPropertyName] =
this[binding.sourceID][binding.sourcePropertyName];

Notice the bracket access with string values. Like I explained before,
this is something that Closure compiler cannot detect.

Technically, I could write another custom Closure compiler pass that looks
for ConstantBinding objects in the MXML data tree and extract the property
names. It wouldn't be hard, but what happens if new types of bindings are
added that work similarly? Do I just keep adding them to the compiler as
special cases? Or what if someone wants to use the compiler with a
different framework and wants to move ConstantBinding to a different
package? Do they need to modify the compiler too? It simply doesn't feel
clean to me. So, if you want to use the
-prevent-rename-public-symbols=false compiler option, I recommend avoiding
ConstantBinding. Regular bindings with curly braces {} are fully supported,
and they're what most people will be familiar with from Flex anyway.

==========
Extra Result: Allow *some* public symbols to be renamed
==========

ASDoc: 210 KB (-27 KB / -11%)
TourDeJewel: 927 KB (-198 KB / -18%)

Compiler options used:
-export-public-symbols=false
-prevent-rename-protected-symbols=false
-prevent-rename-internal-symbols=false
*-prevent-rename-public-static-methods=false*
*-prevent-rename-public-instance-methods=false*

That being said, even when using ConstantBinding or other dynamic features,
it may be possible to rename a subset of public symbols. Most commonly,
public methods are usually safe to rename because most people don't try to
call methods dynamically. This final extra test is consistent with a
similar result from my previous post.

That was really long, so thank you for reading, if you made it to the end!

--
Josh Tynjala
Bowler Hat LLC <https://bowlerhat.dev>

Reply via email to