Bug 2499 - monotouch types are too fat for boehm.
Summary: monotouch types are too fat for boehm.
Alias: None
Product: iOS
Classification: Xamarin
Component: Xamarin.iOS.dll ()
Version: 5.0
Hardware: PC Mac OS
: --- normal
Target Milestone: Untriaged
Assignee: Bugzilla
Depends on:
Reported: 2011-12-14 12:09 UTC by Chris Toshok
Modified: 2014-04-07 14:08 UTC (History)
5 users (show)

Is this bug a regression?: ---
Last known good build:

tool (incl. Mono.Cecil) (113.94 KB, application/zip)
2011-12-14 19:48 UTC, Sebastien Pouliot

Notice (2018-05-24): bugzilla.xamarin.com is now in read-only mode.

Please join us on Visual Studio Developer Community and in the Xamarin and Mono organizations on GitHub to continue tracking issues. Bugzilla will remain available for reference in read-only mode. We will continue to work on open Bugzilla bugs, copy them to the new locations as needed for follow-up, and add the new items under Related Links.

Our sincere thanks to everyone who has contributed on this bug tracker over the years. Thanks also for your understanding as we make these adjustments and improvements for the future.

Please create a new report on Developer Community or GitHub with your current version information, steps to reproduce, and relevant error messages or log files if you are hitting an issue that looks similar to this resolved bug and you do not yet see a matching new report.

Related Links:

Description Chris Toshok 2011-12-14 12:09:01 UTC
If there's a field containing a reference beyond 120 bytes into an object instance (i.e. beyond the point where the bit referring to it would exceed the 30 bits available in the GC bitmap) mono switches to conservative scanning for instances of that type.

MonoTouch bloats up instance sizes with all the __mt_* fields.  Those should be packaged up (per type) and dangled off the instance.

For instance, if you subclass UIViewController (a pretty common occurrence), you pull in these fields:

  MonoTouch.UIKit.UIViewController.__mt_View_var: 4
  MonoTouch.UIKit.UIViewController.__mt_NibBundle_var: 4
  MonoTouch.UIKit.UIViewController.__mt_ModalViewController_var: 4
  MonoTouch.UIKit.UIViewController.__mt_ParentViewController_var: 4
  MonoTouch.UIKit.UIViewController.__mt_TabBarItem_var: 4
  MonoTouch.UIKit.UIViewController.__mt_RotatingHeaderView_var: 4
  MonoTouch.UIKit.UIViewController.__mt_RotatingFooterView_var: 4
  MonoTouch.UIKit.UIViewController.__mt_EditButtonItem_var: 4
  MonoTouch.UIKit.UIViewController.__mt_SearchDisplayController_var: 4
  MonoTouch.UIKit.UIViewController.__mt_NavigationItem_var: 4
  MonoTouch.UIKit.UIViewController.__mt_SplitViewController_var: 4
  MonoTouch.UIKit.UIViewController.__mt_TabBarController_var: 4
  MonoTouch.UIKit.UIViewController.__mt_NavigationController_var: 4
  MonoTouch.UIKit.UIViewController.__mt_ToolbarItems_var: 4
  MonoTouch.UIKit.UIViewController.__mt_Storyboard_var: 4
  MonoTouch.UIKit.UIViewController.__mt_PresentedViewController_var: 4
  MonoTouch.UIKit.UIViewController.__mt_PresentingViewController_var: 4
  MonoTouch.UIKit.UIViewController.__mt_ChildViewControllers_var: 4
  MonoTouch.UIKit.UIResponder.__mt_NextResponder_var: 4
  MonoTouch.UIKit.UIResponder.__mt_UndoManager_var: 4
  MonoTouch.UIKit.UIResponder.__mt_InputAccessoryView_var: 4
  MonoTouch.UIKit.UIResponder.__mt_InputView_var: 4
  MonoTouch.Foundation.NSObject.handle: 4
  MonoTouch.Foundation.NSObject.super: 4
  MonoTouch.Foundation.NSObject.super_ptr: 4
  MonoTouch.Foundation.NSObject.IsDirectBinding: 4

That's 104 bytes toward the 120 byte total already gone.  So if you have 5 or more fields, and the 5th or later one is a gc-able type, you get conservative behavior for the type.

This is, needless to say, terrible.  Reducing it to per-type backing fields gives:

  MonoTouch.UIKit.UIController.__mt_UIKit_UIViewController_backingFields: 4
  MonoTouch.UIKit.UIResponder.__mt_UIKit_UIResponder_backingFields: 4
  MonoTouch.Foundation.NSObject.__mt_Foundation_NSObject_backingFields: 4

down to 12 bytes.  that's a little better.
Comment 1 Sebastien Pouliot 2011-12-14 13:40:20 UTC
Interesting idea :-) 

Note that in the recent MonoTouch releases the (managed) linker will remove unused __mt_* backing fields. The exact number will depend on what is needed by the application being built so YMMV.
Comment 4 Sebastien Pouliot 2011-12-14 19:48:23 UTC
Created attachment 1046 [details]
tool (incl. Mono.Cecil)

Here's a small tool to compute the field size of every types inside an assembly. You can specify the assembly and a size limit (everything >= limit will be shown).

On MT 5.1.2 you'll get something like:

$ MONO_PATH=. mono fieldcount.exe /Developer/MonoTouch/usr/lib/mono/2.1/monotouch.dll 100
33 items of size >= 100

but on something that the linker has removed the extraneous backing fields you should get something a lot better. E.g.

$ MONO_PATH=. mono fieldcount.exe /Developer/MonoTouch/Source/monotouch/tests/monotouch-test/bin/iPhone/Debug/monotouchtest.app/monotouch.dll 50
 64 : MonoTouch.CoreAnimation.CATransform3D
 52 : MonoTouch.UIKit.UITableView
 60 : MonoTouch.UIKit.UITableViewCell
3 items of size >= 50

Of course this will vary a lot depending on the application that was linked.
Comment 5 Chris Toshok 2012-01-21 00:02:07 UTC
Finally got around to running the tool over the linked version:

$ for i in *.dll *.exe; do mono ~/fieldcount/fieldcount.exe $i 120; done | grep "items of size" | awk 'BEGIN { total = 0 } {total += $1} END { print total }'

Some of them quite large (220 bytes is the largest i see here, and there are many > 160 bytes.)  UIView subclasses have 40 bytes tacked onto them because of monotouch.dll backing fields and NSObject data:


UILabel subclasses have 52 bytes added,
UIViewController subclasses also have 40 bytes.

it's also interesting to note that the largest object (220 bytes) is a view controller.  so 180 bytes of that object's size comes from its own instance data, not from monotouch.dll.  But out of that 180 bytes, 80 bytes of it comes from its *own* monotouch generated backing fields in the .xib.designer.cs file.  So in a perfect world, where everything monotouch generated hangs off the object in a separate MonoTouchData object, the instance size of that object would be 104 bytes.  A large object, definitely, but well within boehm's precise scanning capabilities.

class MonoTouchData {
  public bool NSObject_IsDirectBinding;
  public IntPtr NSObject_handle;
  public bool NSObject_registered_toggleref;
  public IntPtr super;
  public IntPtr super_ptr;
  Dictionary<String,NSObject> objectGraph;  // accessed with something like: objectGraph["MonoTouch.UIKit.UIView::Subviews"]

this object is also of course trivially within boehm's abilities to scan precisely.
Comment 6 Rolf Bjarne Kvinge [MSFT] 2012-01-23 09:26:08 UTC
Is this an issue with sgen too, or only boehm?

Besides, changing the code (by increasing complexity and in particular memory usage) to fit the gc seems like a bad thing to do. IMHO the gc should be fixed, not the code.
Comment 7 Chris Toshok 2012-01-23 11:15:27 UTC
the switch to conservative behavior is only a problem for boehm, and that can't be fixed unless gc descriptors (currently fixed at 32 bits) are increased in size.  switching to sgen has its own problems with objects sticking around longer than they should :/

it's also a memory usage optimization, though.  removing a potentially large set of unused fields per instance and shifting it into a ~20 byte MonoTouchData object is a big savings.  it would also mean that the linker optimization for removing backing fields is no longer needed, since NSObject.Dispose can just use a foreach over the objectGraph dictionary.

The bug is that the objects are too fat for boehm.  Fixing boehm to allow huge objects doesn't seem like a fix to me - the fix is to not have huge objects.
Comment 8 Rolf Bjarne Kvinge [MSFT] 2012-01-23 11:50:08 UTC
Ah right, I misunderstood the MonoTouchData idea, I thought you'd just put the same fields in a separate object instance, this is more like SL/ML's (dependency) properties.

As you've said, this will also very likely be saving memory (at the cost of execution speed, which admittedly didn't seem to impact SL/ML that much). I guess the only way to find out is to actually implement it and do some benchmarks.
Comment 9 Sebastien Pouliot 2012-01-23 11:59:46 UTC
The idea has merit but there are drawbacks to consider.

* Extra time indirection (to access MonoTouchData). That should be small but handles are used very often.

* Dictionary access time will be much slower than a field access. Some properties are called quite often.

* A lot of types are small (i.e. few backing fields). Adding a Dictionary for them will make them larger than they are presently (requiring more memory).

* Not having huge objects is not an issue limited monotouch.dll. User code will impact if objects are huge (or not). The above fix won't help huge-objects user code (e.g. stuffing a lot of fields inside a viewcontroller) but fixing Boehm (if possible) would help in all cases.
Comment 10 Chris Toshok 2012-01-24 15:11:13 UTC
info supplied
Comment 11 Sebastien Pouliot 2012-01-27 15:09:47 UTC
note: we already have a Hashtable on all UIControl (see bug #2) that could be merged
Comment 12 Chris Toshok 2012-05-19 12:32:23 UTC
so just to add more data to this *hopefully* not forgotten bug.

This issue is still a problem using monotouch 5.3.3 on another app I'm working on, with sdk linking enabled.  not 50 types in this instance, but still a rather large number at 37 types (mostly viewcontroller subclasses again).

If you want a concrete example you can actually poke at, look at tweetstation.  my (possibly out of date) checkout shows the following for iphone/release:

188 : MonoTouch.Dialog.DialogViewController
124 : MonoTouch.Dialog.GlassButton
132 : MonoTouch.UIKit.UINavigationController
136 : TweetStation.EditAccount
188 : MonoTouch.Dialog.DialogViewController
208 : TweetStation.DetailTweetViewController
160 : TweetStation.Composer
212 : TweetStation.FullProfileView
192 : TweetStation.ConversationViewController
140 : TweetStation.WebViewController
228 : TweetStation.BaseTimelineViewController
240 : TweetStation.TimelineViewController
248 : TweetStation.StreamedViewController
272 : TweetStation.StreamedTimelineViewController
208 : TweetStation.SearchesViewController
272 : TweetStation.SearchViewController
272 : TweetStation.SearchFromGeo
192 : TweetStation.SearchDialog
192 : TweetStation.SearchUser
196 : TweetStation.UserSelector
196 : TweetStation.TwitterTextSearch
248 : TweetStation.StreamedUserViewController
216 : TweetStation.Settings

Note that there's a MonoTouch.UIKit class in there.  so even with linking of sdk assemblies enabled, an app requires enough features that it pulls in enough backing fields to blow the 120 size limit of anything that subclasses from UINavigationController, without any fields of its own.  Some types weigh in at 272 bytes per instance - requiring more than twice the size of the bitmap boehm uses.
Comment 13 Miguel de Icaza [MSFT] 2012-05-20 12:24:28 UTC

How are you measuring the size of that?   I ran the following snippet on TweetStation, and it looks like DialogViewController has 29 fields, some of them booleans, so it would be less than 116 bytes.

GlassButton is 15 fields, so about 60 bytes.

var t = typeof (DialogViewController);
while (t != null){
    foreach (var f in t.GetFields (BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly)){
        Console.WriteLine ("got {1} {0}", f, f.FieldType);
    t = t.BaseType;

For GlassButton the output is:
got System.Boolean System.Boolean pressed
got MonoTouch.UIKit.UIColor MonoTouch.UIKit.UIColor NormalColor
got MonoTouch.UIKit.UIColor MonoTouch.UIKit.UIColor HighlightedColor
got MonoTouch.UIKit.UIColor MonoTouch.UIKit.UIColor DisabledColor
got System.Action`1[MonoTouch.Dialog.GlassButton] System.Action`1[MonoTouch.Dialog.GlassButton] Tapped
got System.Object System.Object __mt_Font_var
got System.Collections.Hashtable System.Collections.Hashtable targets
got System.Object System.Object __mt_BackgroundColor_var
got System.Object System.Object __mt_Layer_var
got System.Object System.Object __mt_Superview_var
got System.Object System.Object __mt_Subviews_var
got System.IntPtr System.IntPtr handle
got System.IntPtr System.IntPtr super
got System.IntPtr System.IntPtr super_ptr
got System.Boolean System.Boolean IsDirectBinding

Another idea worth exploring is to move what we are currently treating as instance data for super and super_ptr to be part of the actual Mono object class.
Comment 14 Chris Toshok 2012-05-20 18:18:40 UTC
the instance sizes are printed from the script sebastien wrote (Comment 4)
Comment 15 Sebastien Pouliot 2012-05-20 20:16:55 UTC
The (quick and dirty) tool was made to count backing fields, which are are 4 bytes each (System.Object references), to see the effect of the linker optimization versus this issue. That assumption won't always be the case for user types (and the tool reports "4 * fields" bytes) so YMMV.

Also the above numbers are furthermore skewed because TweetStation uses MonoTouch.Dialog from GIT, not the MonoTouch.Dialog-1.dll assembly provided by MonoTouch. This means "link sdk" for TS does not link MT.D (not an SDK assembly) but would have linked MT.D-1.dll (an SDK assembly). Since MT.D refers to a lot of (otherwise unused) metadata then all the types are bigger (than using MT.D-1.dll).

I know TS and MT.D are co-developed so it make sense to use GIT (over SDK) but it make it hard to effectively  compare it to other projects using MT.D-1.dll :) So, out of curiosity, I tried removing the reference to MT.D.dll and replacing it with MT.D-1.dll. This shows no type over 120 bytes in both monotouch.dll and MT.D.dll. The (really) close one being:

116 : MonoTouch.Dialog.DialogViewController

while the biggest inside monotouch.dll are:

 64 : MonoTouch.CoreAnimation.CATransform3D
 64 : MonoTouch.UIKit.UITableViewCell

and UINavigationController gets down to:

 40 : MonoTouch.UIKit.UINavigationController

The number of fields above 120 bytes (assuming 4 bytes per field) becomes 13 for TweetStation.

So the more linking there is, the less problematic the situation can be (I did not try "link all" but it can only be identical or better). Still a 120 bytes limit is only 30 references. It's not hard to imagine (or create a test case) that would go over it - but that is true even if MT itself took 0 byte out of the 120.
Comment 16 Chris Toshok 2012-05-20 21:50:36 UTC
wait - the fieldcounts tool fudges and counts everything as 4 bytes?  or it just counts backing fields and nothing else?  confused by the that first line.  I had written my own tool back in december that did the same basic task but at least guessed on field sizes/alignments.

the point of all of this is that simply by using a feature of monotouch someplace in your code, it increases the size of all instances of a particular type and its subclasses.  yes, it's unlikely that people will use every possible feature of some view controller subclass, but that doesn't make the problem go away.  Just putting all backing fields in a different object will likely fix everything.  It will drop monotouch's usage down to 4 bytes of the 120 - always.  and linking will only affect the size of the MonoTouch<Type>Data instances.

That last paragraph basically makes it sound like "since it's possible for the user to blow the limit even if we fix this bug, we aren't going to fix the bug."  Should I just close this?
Comment 17 Miguel de Icaza [MSFT] 2012-05-21 14:04:34 UTC
I think the original proposal has a lot of value, and we should look into implementing it (backing fields), so it is worth keeping this bug open.    The advantage is that even if the backing field structure grew large, and the GC would switch to conservative scanning for those MonoAuxData classes, every single field in those classes would be a managed pointer, so conservative or not conservative for fields in those structures would effectively be identical.

The actual implementation of keeping the fields in an ancillary class is very easy to do, but we need do do this together while updating our linker (these days it is both a linker + optimizer), so it might just take a little of time.

The above made me realize that in many of these samples, the switch conservative mode for these objects probably has very little effect, as doing a conservative scan of these:

* For any managed pointers will just be treated for what they are: pointers to objects, so no difference
* Any unmanaged pointers will never cause a false positive, since no managed object would point there (handle, super, super_ptr)

So the areas that can cause problems are bit patterns like the aggregated booleans that might look like a pointers (the two bytes above).   Since I believe this is the root of the concern, I wonder if there is a test case that we can look in the Rdio app that is causing trouble.

It would be relatively easy to instrument our GC to dump statistics of objects that are being kept alive due false positives (easier than upgrading the GC to handle more than the current limit).
Comment 18 Sebastien Pouliot 2014-04-07 14:08:19 UTC
This issue is resolved when using the New-Refcount (NRC) feature [1]. This is not a new feature [2] but it has totally revamped [3] in the last two months based on the bug reports, like this one.

With XI 7.2.1 this features works with both Boehm (default) and Sgen garbage collectors. NRC has also been enhanced and, right now, there are no known issues (bugs) against it.

While NRC is not the default option (in 7.2.1) we plan to make it so in the near future. Additional testing and feedback on the feature would be appreciated.

[1] http://docs.xamarin.com/guides/ios/advanced_topics/newrefcount/
[2] http://docs.xamarin.com/releases/ios/MonoTouch_5/MonoTouch_5.2/
[3] Newer versions of Xamarin Studio will remove the experimental tag on the feature when XI 7.2.1+ is used.