mrpmorris / Fluxor

Fluxor is a zero boilerplate Flux/Redux library for Microsoft .NET and Blazor.
MIT License
1.27k stars 147 forks source link

Garbage Collection and pefrormance #22

Closed soso11 closed 4 years ago

soso11 commented 4 years ago

I am using Fluxor.Blazor.Web 3.1.0 in my client-side Blazor application. I created state, feature, action, effect and reducer to fetch data from API. Everythink works fine, but I noticed some messages related with GC in browser console and performance problem - my api call takes 5 seconds. But in network tab in browser I can see that API call took 25ms an size is 60kB.

message in browser console: L: GC_MAJOR_SWEEP: major size: 18896K in use: 29384K blazor.webassembly.js:1 L: GC_MAJOR: (LOS overflow) time 38.93ms, stw 38.94ms los size: 25520K in use: 17984K

so I tried to investigate the problem. In first glance I thougth that it is a problem with HttpClient, so I added Stopwatch to measure API call time. Stopwatch stopWatch = new Stopwatch(); Console.WriteLine("fetch start " + url); stopWatch.Start(); var content = await _httpClient.GetStringAsync(url); Console.WriteLine("get in seconds: " + stopWatch.Elapsed.Seconds); StopWatch shows 5 second per request and GC message was displayed in console. So I tryied to do the same in empty application - without dependenceies, just single view and everythink was fine - API call took less than 0 seconds and there was no messages in console. In next steps I tryied to simplyfy my application to find the source of problem. After a few iterations of commenting and uncommenting code I relaized that problem is with State.

When State is created first time in reducer everythink is fine - data is displayed in UI very fast without problems. But when eg. user navigates to other page and after that goes back to first one, component dispathes Action again to fetch fresh data from API and this is fine. But in reducer new State object is created, old one is probably repleaced and this generates the performance problem and GC message in console. When I replaced "return line" in code below with "return new ActiveOrdersState(state.data)" each API call is fast and I do not have GC messages in console, but UI is not updated because State was not changed.

public class GetForecastDataSuccessActionReducer : Reducer<ActiveOrdersState, FetchActiveOrdersResultAction> { public override ActiveOrdersState Reduce(ActiveOrdersState state, FetchActiveOrdersResultAction action) { var data = action.Data; return new ActiveOrdersState(data); } }

I am not sure if it is an issue, but I have problem like that, I read documentation twice and looks like I did everything correctly. Do you know how to fix that?

mrpmorris commented 4 years ago

In your UI, where you display each of the items in the data, are you using the Blazor @key directive? https://blazor-university.com/components/render-trees/optimising-using-key/

Do you see this negative behaviour when you don't render the data to the UI at all?

soso11 commented 4 years ago

Yes, I am using @key direcitve. foreach (var order in State.Value.Data) { <OrderDetails @key="@order.Id" Order="@order" /> } When I don't render the data it looks fine - no messages in console and API data is loading fast.

In OrderDetails component I am using some Radzen controls maybe problem is with some of them...

mrpmorris commented 4 years ago

If the problem doesn't occur when you don't render the data, it suggests the problem lies in the rendering.

I'll close this for now. If you find the error is in Fluxor then please reopen and provide me with new information.

soso11 commented 4 years ago

The problem was with Blazor itself. When Blazor have to rerender big area eg. when we have big object in store and we want to replace it with new one, it takes time and GC messages are visible in console.

mrpmorris commented 4 years ago

Are you using @key to identify components generated in loops?

https://blazor-university.com/components/render-trees/optimising-using-key/

soso11 commented 4 years ago

Yes, I have only one loop and inside I generate only one component with @key.

mrpmorris commented 4 years ago

@soso11 When you say you replace a large object, do you actually need to do that?

For example, if your state in that Feature has a single root object but is actually a tree of thousands of objects, if only one node changes then 99.9% of the object instances can be reused.

soso11 commented 4 years ago

You are right, but I have multiple concurrent users and I need fresh data. Right now I implemented progressive loading based on scroll, but in future I think about some hash or version for each item in array. Based on that I can verify if I need to replace item in store or it is up do date. And in future when mono will be replaced with .net5 it can be faster.

Thanks for now. I think you did a great job, I have some suggestions and ideas for changes in Fluxor based on my experience with it, but in general I think it is very good.