Search

Thursday, September 10, 2009

Global variables are bad

Hyderabad Zoological Park

<This post is about C++ (C# folks are saved from this pain)>

One of our devs taking care of NETCF on Nokia S60 reported that after the latest code reached their branch things are working very slow. It was observed that Garbage Collector is getting kicked in way more than it should. Investigation showed something interesting.

I added a global var gcConfig which had a fairly complex constructor and that among other things sets the various member variables like the GC trigger threshold to default value (1mb). All of these works fine on Windows variants.

However, TCPL states “It is generally best to minimize the use of global variables and in particular to limit the use of global variables requiring complicated initialization.”. This is especially true for dlls. We tend to ignore good advice sometimes :)

For Symbian OS (S60) on device complex ctors of global objects are not run (without any warning). The members are all set to 0 (default member initialization). So in the gcConfig GC-trigger was being set to 0 instead of 1mb. The allocation byte count is compared against this value to figure out when it’s time to start a GC. Since it was 0 the collection condition is being met for every allocation and hence for every allocation request GC was being fired!!!

Actually considering that even after that the app was actually running shows that we have pretty good perf ;)

A good alternative of using global vars is as follows

MyClass& useMC()
{
static MyClass mc; // static ensures objects are not created on each call
return mc;
}

MyClass& mc = useMC();

Do note that this has some multi-threading issue. See http://blogs.msdn.com/oldnewthing/archive/2004/03/08/85901.aspx.

Monday, September 07, 2009

.NET Compact Framework BCL < .NET BCL

Hyderabad Zoological Park

Over twitter some folks are regularly discussing about the fact that there are some chunks of desktop .NET Base class library (BCL) that are missing on the .NET Compact Framework (.NETCF). So I thought I’d post the rationale behind things that are missing and what criteria is used to figure out what makes it in.

First of all, .NET and .NETCF use completely different runtimes. So the BCL code doesn’t work as is. They need to be ported over. Something being available on the desktop reduces the effort of our BCL team but still significant cost is involved in making it work over NETCF. This means that its not as if everything is available on .NETCF BCL and we cut things out (elimination process), but the other way round where we start from 0 and we need to figure out whether we can take something in. In that process we use some of the following rationale.

  1. ROI: Does the user scenario that it enables on a mobile device justify the cost
    System.CodeDom.Compiler. Yea right you expected to compile on the phone didn’t you :)
  2. Easy workaround available: If there is an acceptable workaround for a given API then it drops in priority. A trivial example could be that we ship some method overloads but not the other. E.g. Instead of shipping all overloads of Enum.Parse we drop Enum.Parse(Type, String) and keep only Enum.Parse(Type, String, Bool).
    This applies at the level of namespace or Types as well.
  3. Lower in priority list: It needs work to get it done and it’s lower in priority than other things that is keeping the engineering team busy.
    If there are a lot of request for these features and not good/performant workarounds available then it will go up the priority list and make it to future version of NETCF
  4. Too large to fit: Simply too large to fit into our memory limitations
    E.g. Reflection.Emit which leads to other things missing like Dynamic Language Runtime, Linq-to-SQL
  5. No native support: It uses some underlying OS feature which is either not present on devices or is not relevant to it
    WPF, Parallel computing support

With every release developers ask for new features and we also negotiate to increase NETCF footprint budget so that we can include some (but not all) from those requests. To choose what makes it in we use some of the criteria mentioned above.

Given the system constraints under which NETCF works a vast majority of the desktop APIs will continue to be missing from NETCF. Hopefully this gives you some context behind why those are missing. If you approach NETCF simply from trying to port a desktop application then you would face some frustration on the missing surface area.

BTW: Do post your comments on this blog or on twitter (use the #netcf hashtag).

<Update: I made some updates to this based on feedback />

Tuesday, September 01, 2009

NETCF: GC and thread blocking

Hyderabad Zoological Park

One of our customers asked us a bunch of questions on the NETCF garbage collector that qualifies as FAQ and hence I thought I’d put them up here.

Question: What is the priority of the GC thread
Answer: Unlike some variants of the desktop GC and other virtual machines (e.g. JScript) we do not have any background or timer based GC. The CLR fires GC on the same thread that tried allocation and either the allocation failed or during pre-allocation checks the CLR feels that time to run GC has come. So the priority of the GC thread is same as that of the thread that resulted in GC.

Question: Can the GC freeze managed threads that are of higher priority than the GC thread?
Answer: Yes GC can freeze managed threads which has higher priority than itself. Before actually running GC the CLR tries to go into a “safe point”. Each thread has a suspend event associated with it and this event is checked by each thread regularly. Before starting GC the CLR enumerates all managed threads and in each of them sets this event. In the next point when the thread checks and finds this event set, it blocks waiting for the event to get reset (which happens when GC is complete). This mechanism is agnostic of the relative priority of the various threads.

Question: Does it freeze all threads or only Managed threads?
Answer: The NETCF GC belongs to the category “stop-the-world GC”. It needs to freeze threads so that when it is iterating and compacting managed heap nothing on it gets modified. This is unlike some background/incremental GC supported by other virtual machines.

GC freezes only managed threads and not other native threads (e.g. COM or host-threads). However, there are two exclusions even on the managed threads

  1. It doesn’t freeze the managed thread that started GC because the GC will be run on that thread (see answer to first question)
  2. Managed threads that are currently executing in native p/invoke code is not frozen either. However, the moment they try to return to managed execution they get frozen. (I actually missed this subtle point in my response and Brian caught it :))

Hope this answers some of your questions. Feel free to send me any .NET Compact Framework CLR questions. I will either answer them myself or get someone else to do it for you :)