Android Chart (Xamarin edition): Out of GREF


#1

Hello,

I am evaulating Android Chart and have modified on of the supplied samples (Pan and Zoom weather data) to load more data (30 years EOD data), and it is really slow adding datat to the SimpleDataAdapter - many seconds in the emulator and 10s of seconds on a real device. Once the data is loaded and bound to the chart, it runs really smooth.

The reason for this slow behavior seems to be related to the logging:

11-04 08:48:54.006 D/dalvikvm( 3736): GC_EXPLICIT freed 451K, 18% free 8378K/10164K, paused 0ms+1ms, total 9m
11-04 08:48:54.886 I/monodroid-gc( 3736): 46800 outstanding GREFs. Performing a full GC!
11-04 08:48:54.978 D/dalvikvm( 3736): GC_EXPLICIT freed 981K, 23% free 7876K/10164K, paused 0ms+1ms, total 6ms
11-04 08:48:55.806 I/monodroid-gc( 3736): 46800 outstanding GREFs. Performing a full GC!
11-04 08:48:55.886 D/dalvikvm( 3736): GC_EXPLICIT freed 245K, 24% free 7748K/10164K, paused 0ms+0ms, total 5ms

this GC logging continous.

I guess the reason is that for every data point that is added a DataPoint is created (implenents IJavaObject) aswell as a new Java.Util.Data (also implenents IJavaObject). All these causes a cross VM reference to be created and very soon the GREF max is hit causing this extensive GC activity.

I am lookin for some more lightweigth way of passing data to the chart than having to create all those cross VM objects.

Reards
Lars Krog-Jensen


#2

Hi Lars,

Could you let us know how many points you are adding, and possibly a snippet of the code you are using to do it, please? I’m wondering if you are using the Add method inside a loop, hitting it once for each data point. That would cause the Xamarin bindings to go through the JNI for every point, and would cause  the chart to be fully reloaded every time, which would load the processor in proportion to the square of the number of data points. A possible alternative would be to use the AddAll method, or if you’re adding data continuously to roll your own custom data adapter (see the CustomDataAdapter sample). 

Best Regards

Robin Sillem


#3

Hi Robin,

Well, yes I was adding data point to the SimpleDataAdapter from within a loop, but moving it out of the loop did not make any measurable difference. And my understanding of the mono android binding is that every .Net object that extends/implenents a IJavaObject will allocate MCW (includes a GREF) and thus lives both in the Android VM aswell as in the Mono VM (please correct me if I am wrong). These allocations are expensive. In the sample I add ~9000 DataPoint:s and each data point includes a Java.Util.Date, this will sum up to 18 000 cross vm allocations. I.e. it is not the SimpleDataAdapter.Add(…) that is expensive.

I zipped the VS project and uploaded to sendspace, (remove my trial license key):
http://www.sendspace.com/file/ebreh1

The sample is based on the PanAndZoom sample activity where I have changed it somewhat into two activities, on that load approx 9000 end-of-day data and on activity that loads intraday data, approx 12000 points. I run the app with Genymotion and hit historical data button, wait for data to load (2-3 seconds in the emulator), go back and then hit intraday button (takes forever). 

There could be that some data points are not properly released (disposed) and therefore we hit the ~46 GREF limit very quick.

Please note, that the code is very ‘hacky’ just to try out the functionallity.

Regards
Lars Krog-Jensen


#4

Hi Lars,

Yes, the time is almost all taken up in populating the array you give to the chart. By splitting the stpowatch timing in 2, I see

[IntraChartActivity] Populating took: 11278 - this is in building the points list

[IntraChartActivity] Loading took: 283 - this is in adapter.AddAll

the first time, then

[monodroid-gc] 46800 outstanding GREFs. Performing a full GC!

[monodroid-gc] 46800 outstanding GREFs. Performing a full GC!

[IntraChartActivity] Populating took: 32469

[IntraChartActivity] Loading took: 200

I’m not quite sure what to do about this. Have you tried breaking the data into smaller chunks, maybe loading 1000 points into the chart at a time, reusing data point objects? I had a look at the Xamarin pages, but they weren’t much help, really.

Regards,

Robin Sillem


#5

Hi Robin,

Loading data in chunks would probably not help, I guess the real problems is that each datetime/value pair requires two managed objects living in both VM’s.

To avoid this would probably require a different kind of DataAdapter, if  - for example - there would be a

TimeseriesAdapter that had:
Add(long epoc, double value) 
and
AddAll(long[] epoc, double[] values)

Behind the scens, on the java side, you could still use the standard DataAdapter<Date, Double>, but this way we could avoid having duplication of objects.
Think of this adapter as  mono proxy adapter tailord for efficient transfer of data.

An alternative could be a utility factory class: DataAdapter  DataAdapterFactory.BuildFromData(long[] epoc, double[] value)

Regards
Lars Krog-Jensen


#6

By the way, its my understanding that you (Shinobi Controls) has a good relationship with Xamarin, maybe you could ask them what would be the best solution for this kind of problem.

Regards

Lars


#7

Hi Lars,

One thing that occurred to me on the way home: try using DataPoint itself rather than your subclass. In the Xamarin.iOS case we found that the Xamarin bindings would allow the .NET side instances of directly bound classes to be garbage collected while the native objects were alive, because they could simply be rehydrated at any time. Derived classes however could not be treated in this way because they might have state in the .NEt world which was not present in the native instance.

Regards,

Robin


#8

Hello Robin,

I am afraid that it did not make any difference, any more suggestions?

Regards
Lars


#9

Hi Lars,

I’m actually quite liking the idea of a DataAdapter designed to be efficient in the Xamarin context. Could you do some timings on marshalling your data into a flat array of primitives for each of X and Y (an array of DateTimes would also work, as I could put methods into the DateUtils class to turn them into an array of long or double), and I’ll have a look at how simple it would be to do a new DataAdapter class on the Java side (or add methods to SimleDataAdapter), with bindings? We have V1.2 coming out in a couple of weeks, so we should be able to get this in to that release. If there’s anything else that would help with your scenario, please let me know.

Regards,

Robin


#10

Hi Robin,

I am not sure how I would go about timing this without setting up an java android project and then create a mono binding for it, I am not up for this for the time being - although it would be nice to learn. But I would be happy to beta test, if that helps.

I also think that we should avoid trying to pass DateTime to java as these structs would have to be wrapped into some java object. Only plain primivitves should be used, and therefore epoc would be best. Anything that inherits or touches a java.object will cause a cros-vm reference, and thus usage of GREFs.

I read up on seom links on trying to understand this better:
http://docs.xamarin.com/guides/android/advanced_topics/garbage_collection/
http://xamarin.com/evolve/2013#session-0w86u7bco2
http://fizzylogic.nl/2011/08/31/mono-for-android-memory-management-tips/

Regards
Lars


#11

Hi Lars,

Don’t worry, I can do it with your sample that you sent me. I’ll be looking at collecting your data into a flat array of long or double before sending it to the chart, so we are only passing a single array of primitives over the managed/native boundary (well, one for X and one for Y) rather than thousands of java objects. This will require some additional DataAdapter API.

Thanks for those links, most interesting. I’d seen the firast one, but not the others. 

Regards,

Robin


#12

Hi Lars,

I’ve been in touch with Xamarin, and they have made a number of useful suggestions, one of which applies to your existing code.

This is to Dispose the Java.Util.Date and Java.Lang.Double as soon as they are added to the data point - once they are added to the DataPoint, the native side object is referenced, and the managed side object can be GC’d. This will ensure that the Date and Double GREFs are immediately freed, reducing the GREF count from 3 to 1. 

Similarly, the DataPoints can be disposed as soon as they have been added to the DataAdapter, for the same reason. 

This works as long as no subclassing is required, e.g. DataPoint can be used unchanged - your StockDataPoint won’t allow this to work…

While this reduces GREF use and corresponding GC times, it won’t help much with JNI execution time. Creating each DataPoint instance involves 7 JNI invocations (3 instance creations + 2 field settings + 2 Dispose()s).

Overall, they found the performance figures unsurprising, given the JNI invocation overhead. Basically this is part of the trade-off involved in using Xamarin bindings.

Architecturally, I see that your code creates all the data, including reading it in from a csv file, at the moment the user first clicks the button. Obviously, this was just sample code to illustrate the issue, but would it be possible to do this in advance?

We will be looking at providing some Xamarin-specific extensions to the DataAdapter classes, and I have placed it on our backlog. However this work is competing in priority with a number of core features requested by many of our customers and will need to be compatible with the multi-valued data points required for financial series in future releases. We will not, therefore, be adding it immediately.

Best regards,

Robin Sillem


#13

Hi Robin,

Thanks for your update and that you have been discussing this with Xamarin, highly appreciated. I did come to a somewhat similar conclusion that if I dispose some of the objects in some timely manner, I could at least avoid hitting the 48600 GREF ceiling. But as you said, it will be really slow with all the JNI calls going on, and on a somewhat older device the delay of loading is not acceptable. (In my iOS (Xamarin aswell) app version everything works really smooth, no delay what so ever even on my old iPhone 4, and that is impressing.)

The Xamarin approch is fine when the objects being share are coarsed grain, such as a Chart and Axis, but not when being as fine grained as points in a large timeseries. I would like to compare JNI with remote calls, you wouldn’t be doing a remote call for each data point in a  timeseries.

As you concluded, the sample code was reading data from the csv file only for demoing the problem. In real world I will be reading timeseries data from a web service delivering protobuf stuff. Doing these things upfront is not a way forward as there are milions of different the user might chose from.

I will download the 1.2 version and play around with it. We will still purchase the cross platform bundle, and cross my fingers that you will be able the provide Xamarin friendly adapters in the near future. For the time being I will be rolling something out myself.

Again many thanks for your answer and engagement.

Lars


#14

Hi Lars,

I’ve gone through this thread. I’ve a similar problem only a bit more sevre. I need to add around 40,000 datapoints (dateTime - double pairs) or more and I need to convert and scale each value thats being added knocking out the option of adding AddAll. Therefore I constantly breach the GREF threshold.

Considering this thread is almost 3 years old, I contacted shinobi again hoping for a solution. I was only disappointed when they repiled with this thread again.

I would glad to know if you found any possible work around for this problem.

Best Regards,

Krishna. 


#15

Hello Krishna

Please may I ask, have you tried any of the workarounds which Xamarin have recommended (mentioned in this thread)?

You may also find the code examples provided in this thread useful:

As I am sure you can appreciate we do base our development roadmap on customer demand. In this case demand for a Xamarin specific API to address this issue has been low and therefore new features and products have taken priority.

We will of course monitor demand for this feature and may choose to offer it in the future. If you would like us to directly inform you if this becomes available please contact us directly at info@shinobicontrols.com.

Thanks,

Kai.


#16

Hi Kai,

I did try the work arounds like 

  • addAll(),
  • reducing the number of data points and adding more only when zoomed (to improve the granularity),
  • limiting the data points to the current displayed range and updating the adapter on panning,
  • disposing the datapoints, 

Doing all this helped me to avoid breaching the GREF threshold but still there is a huge delay due minor GCs. 

Regards,

Krishna.

 


#17

The dataAdapter is updated based on the gestures and currentDisplayedRange so as to limit the dataPoints. The flip side of this being the minor GCs, invoked each time the adapter is updated.


#18

Hi Krishna,

I’m glad to hear you’ve managed to avoid the GREF threshold problem but appreciate there are still issues with GCing everytime you load in a new chunk of data.

I’ve made sure your voice has been added to the requests for a more Xamarin friendly solution to this and we’ll continue to monitor its popularity and potentially consider it for future development.

Kind regards,

Patrick


#19

Hi Patrick,

Thanks for adding the request to the queue. I hope it gains popularity and you consider it very soon for development as its really limiting the possibilites of the chart. It effectively means that all the operations have to be done on the already loaded dataAdapter. Updating the dataAdapter based on UI is miserable. Any improvement would be highly appreciated.

Best regards,

Krishna.