Gittrends lags: A Tale Of CollectionView and Garbage Collector

Previously on Game of Leaks

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.

Normally 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 GC(0).
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 GC(0).
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

to 92512k

GC_MINOR: (Nursery full) time 23.63ms, stw 35.64ms promoted 1960K major size: 92512K in use: 89592K los size: 2048K in use: 912K


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.
Switch PancakeView and MaterialFrame to simple Frame.

But each time the GC major size keeps growing.

At the end I just had a CollectionView of 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 Xamarin.Android RecyclerView (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: