Previously on Game of Leaks
I'm super interested by what is causing precisely the many GC collections. Will try to dig into this. https://t.co/qnrfFMSZjR— Jean-Marie Alfonsi (@Piskariov) July 14, 2020
So here is what I dug so far:)
First I forked the Gittrends repo, and checkout the main branch before the commit about increasing nursery size (from 8M to 64M).
What I noticed right away is that when scrolling, the GC size keeps increasing. So for each full scroll we have 3 to 5 minor GC which could lead to lags.
How a Garbage Collector works
Warning: I am no GC expert, but I had to face some GC issues during my career. Anyone with more expertise on the subject can reach me and I will be more than happy to fix the mistakes :)
You have to know that there are several levels in a GC.
Levels 0, 1 and 2.
Each of those levels hold references to "alive" objects.
Level 0 is for objects with a short lifetime, level 1 medium, level 2 World War II objects.
Collection time of a given level increases with the level.
For readiness, we will call
GC(0) collection of level 0,
GC(1) of level 1,
GC(2) level 2.
GC(0) is very cheap, and this is why you always been asked to release your c# objects as soon as you can.
The longer your objects stay in memory, the higher it will have an impact on your program performance (spoiler alert: a bad one).
So, if the lifetime of an object is very short, it stays in level 0, and will be released when the level 0 is full.
Then you will have a Garbage Collection of those level 0 with short life items but it will be very light.
Now at this point, maybe some of the objects of the level 0 will still be referenced in your program, and cannot be collected in
They will be then promoted to level 1.
Back to our logs
Here the "Nursery" seems to be the level 0, a
GC_MINOR: (Nursery full) message seems to lead to a
GC_MINOR meaning a
You can also see in the logs that a big chunk of level 0 items are promoted to level 1
promoted 1968K major size: 43744K. I think that the Major size here is maybe the size of level 1 collection. I'm not sure if the Xamarin.Android has a level 2 collection.
We can see that even
GC_MINOR can have a huge impact on your app performance:
GC_BRIDGE: Complete, was running for 137.75ms
Even if it's a "concurrent" GC, a 138 ms operation will surely impact your smooth 60 fps scrolling.
So what we want to avoid is having an everlasting growing collection of objects entering our GC data.
Cause it will induce a lots of GC, meaning a lots of 100ms interruptions (for the gittrends app case).
In this example in one minute of continuous scrolling, the level 1 GC data size went from 4272k
GC_MINOR: (Nursery full) time 22.35ms, stw 24.59ms promoted 1802K major size: 4272K in use: 3525K los size: 4096K in use: 3435K
GC_MINOR: (Nursery full) time 23.63ms, stw 35.64ms promoted 1960K major size: 92512K in use: 89592K los size: 2048K in use: 912K
THIS IS SILLY!
Back to our app
So I tried to find the guilty fella:
- Would it be the FFIL ImageCached?
- The FFIL SvgImage?
- Steven's PancakeView?
- Sharpnado's MaterialFrame?
Well guess what, I commented every single third party components.
MaterialFrame to simple
But each time the GC major size keeps growing.
At the end I just had a
Grid of empty
Frame, but the GC kept growing...
After 1 minute of scrolling through this empty collection view, the major data size (I guess level 1) went from 4352k to 44512k.
The case is still opened
At this stage, I guess now some investigations could be made by the
Xamarin.Forms team to understand what is making the GC size continuously increasing... But I'm afraid it could have to do with how the
Xamarin.Android GC is implemented, and how it processes "peer objects" (native java objects attached to plain c# objects with JNI).
I guess the next test would be to try to use a simple
CollectionView is implemented with it) with empty cells and see if it gives the same result.
Here is the github fork for the Gittrends empty cells causing GC growth: