fabulous-dev / Fabulous

Declarative UI framework for cross-platform mobile & desktop apps, using MVU and F# functional programming
https://fabulous.dev
Apache License 2.0
1.15k stars 121 forks source link

Reduce allocations #805

Closed TimLariviere closed 4 years ago

TimLariviere commented 4 years ago

It's a concurrent PR to #802 because I'm testing a different approach. Ultimately only one PR will be merged, containing the best of the 2.

I have been profiling FabulousContacts (slightly modified version) on Windows, in an Android emulator.

Here's the list of changes I made

Here's the result as of today (left: Fabulous 0.57, right: this PR)

By allocations image

By methods image

By assemblies image

Top allocations image

TimLariviere commented 4 years ago

/azp run full build

azure-pipelines[bot] commented 4 years ago
Azure Pipelines successfully started running 1 pipeline(s).
vshapenko commented 4 years ago

for updateIncremental would be worth take a look at https://github.com/fsprojects/Fabulous/pull/780 to reduce unneccessary allocations, i think

TimLariviere commented 4 years ago

@vshapenko For now, I'm currently looking at reducing allocations in Collections.diff, as this is where most allocations come from.

TimLariviere commented 4 years ago

Ok, I managed to reduce allocations in Collections.diff with a dirty trick. The problem was essentially the fact that Collections.diff and Collections.adaptForObservableCollection were returning an F# list with Operation to apply to a list to go from State A to State B. Those functions are called a lot of times (~20.000 times just for adding 20 people to the contact list in FabulousContacts), so any allocation quickly become a lot of allocations...

So to avoid having those returned F# lists, I preallocated an array from the ArrayPool for the maximum size the algorithm will need and made diff/adaptForObservableCollection directly work with it.

Also made sure that all local functions were not implicitly capturing variables to avoid allocations.

I will need to clean up this algorithm before merging.

By top allocations image

By methods image

By assemblies image

Top allocations image

vshapenko commented 4 years ago

Looks very promising. Looking forward to try this merge in real action

TimLariviere commented 4 years ago

@vshapenko Yes! I will surely publish a preview release.

TimLariviere commented 4 years ago

Fabulous has to box struct to store them in ViewElement because it stores any kind of value as obj. So, to reduce allocations on structs (Xamarin.Forms.Color, Xamarin.Forms.Thickness and Xamarin.Forms.LayoutOptions), I put memoization mechanism by storing the boxed values and reusing those instead of reboxing each time.

Also I've been bit by https://github.com/dotnet/fsharp/issues/526 when trying to reduce struct boxing. So to avoid implicit boxing of structs like ValueOption, I replaced the = operator with a custom function.

By top allocations image

By methods image

By assemblies image

Top allocations image

TimLariviere commented 4 years ago

This time, I changed the way we update Attached Properties to avoid passing an overload dictionary each time we update a ViewElement. Instead the overloaded updater is a function stored in ViewElement just like update.

This removed instantiations of the 5th and 7th most instantiated types, but with apparently little effect on the global memory allocation.

By top allocations image

By methods image

By assemblies image

Top allocations image

vshapenko commented 4 years ago

@TimLariviere , preview is ready to try?

TimLariviere commented 4 years ago

It's almost ready. Just need to fix the few comments I made and then I'll merge this PR and push the preview package to NuGet. Should be done by the end of day.

I'll continue working on allocations in a next PR.

TimLariviere commented 4 years ago

/azp run full build

azure-pipelines[bot] commented 4 years ago
Azure Pipelines successfully started running 1 pipeline(s).
TimLariviere commented 4 years ago

/azp run full build

azure-pipelines[bot] commented 4 years ago
Azure Pipelines successfully started running 1 pipeline(s).
TimLariviere commented 4 years ago

/azp run full build

azure-pipelines[bot] commented 4 years ago
Azure Pipelines successfully started running 1 pipeline(s).
TimLariviere commented 4 years ago

/azp run full build

azure-pipelines[bot] commented 4 years ago
Azure Pipelines successfully started running 1 pipeline(s).
TimLariviere commented 4 years ago

/azp run full build

azure-pipelines[bot] commented 4 years ago
Azure Pipelines successfully started running 1 pipeline(s).