TimLariviere / Fabulous-new

Fabulous v2 - Work in progress
https://timothelariviere.com/Fabulous-new/
Other
41 stars 3 forks source link

Improve linking #47

Closed TimLariviere closed 2 years ago

TimLariviere commented 2 years ago

Fixes #26

Short explanation (proper explanation to come later): Following @twop's advice in #26, I made the creation of AttributeDefinitions and WidgetDefinitions lazy by putting them in get-only static properties instead of let in modules. This was how Don Syme did it in Fabulous v1. The definitions are created only the first time we access that get-only property. Any subsequent access will only do a lookup in a cache.

Preliminary results

I published a version of CounterApp closer to the one in v1 with Ad-Hoc|iPhone configuration and Link All enabled.

Estimated package sizes

Branch Estimated AppStore size Improvement
main 35.43 MB -
improve-linking 34.95 MB 0.48 MB (-1%)

Compiled file sizes

File main (bytes) improve-linking (bytes) Improvement (bytes)
CounterApp.iOS 25 076 960 24 742 736 334 224 (-1,33%)
FSharp.Core.dll 1 459 712 1 456 128 3 584 (-0,25%)
Fabulous.XamarinForms.dll 358 912 337 920 20 992 (-5,85%)
FSharp.Core.aotdata.arm64 342 216 330 712 11 504 (-3,36%)
Fabulous.XamarinForms.aotdata.arm64 190 296 85 096 105 200 (-55,28%)
Fabulous.aotdata.arm64 172 880 178 432 -5 552 (+3,21%)
Fabulous.dll 141 824 141 824 0 (0,00%)
CounterApp.aotdata.arm64 71 224 57 608 13616 (-19,12%)
CounterApp.dll 35 328 18 944 16 384 (-46,38%)
CounterApp.iOS.exe 9 728 9 728 0 (0%)
CounterApp.iOS.aotdata.arm64 1 536 1 536 0 (0%)

Benchmarks for branch main

Method depth Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
ProcessMessages 10 147.8 ms 1.81 ms 1.60 ms 40500.0000 14000.0000 2000.0000 138 MB
ProcessMessages 15 2,829.1 ms 50.57 ms 47.31 ms 505000.0000 168000.0000 69000.0000 1,537 MB
CreateWidgets 10 535.5 μs 5.08 μs 4.75 μs 227.5391 109.3750 - 614 KB
CreateWidgets 20 105,111.3 μs 1,492.97 μs 1,396.53 μs 22400.0000 4200.0000 1000.0000 75,820 KB

Benchmarks for branch improve-linking

Method depth Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
ProcessMessages 10 321.1 ms 5.68 ms 6.08 ms 78000.0000 30000.0000 6000.0000 233 MB
ProcessMessages 15 6,643.2 ms 77.35 ms 68.57 ms 633000.0000 237000.0000 68000.0000 2,591 MB
CreateWidgets 10 631.1 μs 1.85 μs 1.73 μs 240.2344 117.1875 - 627 KB
CreateWidgets 20 117,790.3 μs 1,187.92 μs 1,111.18 μs 22800.0000 4800.0000 1200.0000 77,482 KB

This allocates way more than I expected

twop commented 2 years ago

That is an awesome data. Let me tinker about this for a little bit. I feel like this is an important one, because it can impact public API and architecture.

twop commented 2 years ago

I think there is a world where Attributes and Widgets carry explicit reference to a definition (plus maybe some other metadata). If that is architecturally sound and efficient that might be a way to make it tree shakable.

@TimLariviere do you know what happens if you do this:

// Attributes.define is a pure function, does not modify any global state
let definitionA = Attributes.define<string> (....)
let definitionB = Attributes.define<string> (....)

// but use only "A"
let attrA = definitionA.WithValue(...)

If Attributes.define is pure will definitionB be linked out?

TimLariviere commented 2 years ago

If Attributes.define is pure will definitionB be linked out?

Not 100% sure, but it should be

TimLariviere commented 2 years ago

I updated the sizes data. Found it odd we manage to shave off 20% of the package size just by improving tree shaking in F.XF. Turns out it's not that impressive anymore.

Guess I misconfigured the main branch and it was not using the linker.

But we still get a respectable 0.5 MB off the package size. Which is closer to the relative size of Fabulous vs Xamarin.

Decompiler sources show that unused code is deleted 🥳

TimLariviere commented 2 years ago

From what I understand, when we'll be able to target .NET 6.0, we'll benefit from the linker being able to remove FSharpOptimizationData and FSharpSignatureData from the F# dlls. Which will give us a noticeable drop in dll size.

Downloading from decompiler.com, it gives me:

So knowing Fabulous.XamarinForms.dll is 330 KB, it would be only 13 KB at the end 😱 (That's surely a gross oversimplification, given it's most likely compressed but it still think it would be a major reduction)

https://github.com/dotnet/linker/pull/1040

twop commented 2 years ago

It seems that investing more time in here might be not worth it at the moment, right?

I will tinker if we can remove "Key" concept in the background, if that is indeed possible then we can tree shake unused widgets and attributes.

TimLariviere commented 2 years ago

It seems that investing more time in here might be not worth it at the moment, right?

Yes, it's not being a major improvement. But I still want to see where I'm allocating more because I need it for #42