Bitmaps in the Compact Framework

The Bitmap class in the Compact Framework is a confusing thing, largely because it has abstracted what the OS is doing underneath a little too far.  For example, look at the following code:


Bitmap bmp1 = new Bitmap(fileStream);
Bitmap bmp2 = new Bitmap(200, 200);


Let’s assume that fileStream is a valid stream to a resource bitmap file that is 100×100 in size.  So is there any difference between bmp1 and bmp2, other than the fact bmp1 presumably has some color data in it?  The answer is yes – there’s a very big difference, and that difference can have a huge impact on application performace as well as cause exceptions.


So let’s look at this a little deeper with some examples.  Here’s the first:


int iterations = 0;

while (true)
{
  try
  {
    iterations++;
    Bitmap b = new Bitmap(GetImageStream());
    if (iterations % 100 == 0)
    {
      Debug.WriteLine(string.Format(“{0} objects”, iterations));
    }
  }
  catch
  {
    Debug.WriteLine(string.Format(“Failed after {0} objects”, iterations));
    Debugger.Break();
  }
}


If I run this code (GetImageStream() just pulls an image from an embedded resource), the app will run forever, occasionally spitting out the debug port how many hundreds of objects it’s created.  If you run RPM on it you’ll see memory getting allocated, the GC firing occasionally and resources being freed up.  All is well in the world of managed code and everything is working as expected.  Hooray.


Now let’s change that ever so slightly to this:


int iterations = 0;

while (true)
{
  try
  {
    iterations++;
    Bitmap b = new Bitmap(200, 200); // <— CHANGED HERE
    if (iterations % 100 == 0)
    {
      Debug.WriteLine(string.Format(“{0} objects”, iterations));
    }
  }
  catch
  {
    Debug.WriteLine(string.Format(“Failed after {0} objects”, iterations));
    Debugger.Break();
  }
}


Note the single change where the bitmap is created.  Try running this and after a few iterations – the exact number depends on available device memory – it will OOM (throw an out of memory exception).  On the device in front of me it was about 40.


So the first thing to do is theorize why this would happen.  Seems like the Bitmap’s resources aren’t getting freed after it goes out of scope at the end of the while block.  An explicit call to Dispose() may solve it if that’s the case, so let’s try another test.


int iterations = 0;

Bitmap b = null;
while (true)
{
  if (b != null)  // explicit disposal
    b.Dispose();

  try
  {
    iterations++;
    b = new Bitmap(200, 200);
    if (iterations % 100 == 0)
    {
      Debug.WriteLine(string.Format(“{0} objects”, iterations));
    }
  }
  catch
  {
    Debug.WriteLine(string.Format(“Failed after {0} objects”, iterations));
    Debugger.Break();
  }
}


Sure enough, when we run this, it behaves like the first.  Strange that the Bitmap behaves differently depending on which constructor we use – this is contrary to common sense, right? 


So let’s think a little more.  A Bitmap has a large area of unmanaged resources and some managed resources.  It seems that when we create a bitmap using the size ctor, the finalizer doesn’t get run when an OOM happens.  Let’s test again and see if that really is what’s going on. We’ll remove the explicit Dispose call and wait for the finalizers and try again when we OOM.


int iterations = 0;

while (true)
{
  try
  {
    iterations++;
    Bitmap b = new Bitmap(200, 200);
    if (iterations % 100 == 0)
    {
      Debug.WriteLine(string.Format(“{0} objects”, iterations));
    }
  }
  catch (OutOfMemoryException)
  {
    Debug.WriteLine(“Waiting for finalizers to run…”));
    GC.WaitForPendingFinalizers();
    Bitmap b = new Bitmap(GetImageStream());
  }
}


When we run this one, again all is well in managed code land, though we see it waiting for finalizers to run a lot, and that catch is an expensive one for perf (as all exceptions are). At this point I think “well that surely has to be a bug” but I often like a second opinion, so I went right to the source and asked the CF team about the behavior.  The response from them is actually quite informative.  Their response in in italics below.



I think you are probably seeing is several interactions that can be quite confusing.



  1. Creating a bitmap using the stream constructor will construct a DIB (Device Independent Bitmap).
  2. Creating a bitmap using the width/height constructor will construct a DDB (Device Dependent Bitmap).
  3. DIB’s are allocated out of the virtual address space of the application.
  4. DDB’s are allocated by the driver. This typically means that they are allocated in the virtual address space of gwes.exe. Alternatively, the driver could allocate these in dedicated video ram.
  5. Creating a bitmap with the stream constructor will generate a fair amount of garbage as it copies data from one buffer to the other.

When we perform a GC because of an OOM in the stream constructor case, we will almost certainly have some amount of garbage that we can free back to the OS immediately. This will also trigger the finalizer to run on another thread as soon as possible. That should help the next call to bitmap creation.


When we perform a GC because of an OOM in the width/height constructor case, it is fairly likely that the OOM is caused because of virtual memory exhaustion in gwes.exe. Thus freeing memory in our process will not help the memory condition in gwes.exe. We need the bitmap finalizer to run before this would actually free memory in a way that would help this scenario. While the finalizer thread would certainly have been triggered to start, it most likely will not get a chance to free bitmaps before we OOM while trying to allocate a bitmap immediately after triggering a GC on the initial thread.


In short, we have 2 different types of Bitmap in our runtime with varying performance and allocation characteristics. DDBs are generally faster to manipulate and draw to the screen than DIBs, but they are constructed in an external memory space that can cause allocation confusion and cause the performance of calls to LockBits or Save to be slow. If a DIB is desired and you wish to construct it based on width and height, we provide a function that constructs a Bitmap with a width, height, and pixelformat specified. This function will construct a DIB instead of a DDB.


I personally still consider this a bug in the implementation – the CF should catch these occasions and handle it for us rather than OOMing all the way back to the app to wait for the Finalizers and retry – that’s an implementation that should be done below us. 


Still the answer sheds light on the fact that how we create a Bitmap should be highly dependent on how we intend to use that Bitmap.

4 Comments

  1. It’s not a bug in the Framework. You should always Dispose anything that (ideally) implements IDisposable, or has a Dispose or Close method (not so good). Use the ‘using’ statement where possible. If you have any members of your class that need to be disposed, you should implement IDisposable and dispose of your instance members in your Dispose override, if the ‘disposing’ parameter is true.

    Following these rules consistently – for all objects, not just Bitmaps – will give the best results. Yes, I know this is against the promise of GC, that you don’t have to worry about managing memory.

    The desktop .NET Framework 2.0 added a new concept of ‘memory pressure’ for the GC. Calling GC.AddMemoryPressure allows you to tell the GC about unmanaged objects which may cause it to run a GC earlier than its normal heuristics would suggest. Sadly, Compact Framework does not yet support this.

  2. Whether or not it’s a bug is fully a matter of opinion. I fully agree that "good" behavior is to call Dispose on objects that expose it whenever you’re done with them. That said, the reason for doing that is becasue you know better than the GC when the resources are no longer needed. Calling Dispose is purely a hint to the GC. Not calling it may incur a perf hit when not expecting it, but it should *not* result in an OOM when a collection would prevent the OOM. The fact that different constructors of the same class yield different behavior on this only solidifies my thinking that it’s a bug.

  3. Hey all,

    First off, thank you. I no longer consider this a bug, rather, I
    consider it a caveat that any .NET CF developer using Bitmaps must be
    aware of.

    Chris, as you suggested in your blog, I spent a bit of time massaging
    the code listed to see how the memory responds. I used the .NET
    Compact Framework RPM of CF 2.0 SP1 & perfmon to display a running
    account of GC statistics. I also used the Win CE RPM v5.0 that comes
    with EVC 4.0, the tool the .Net CF RPM seems to have replaced.

    For the WinCeRPM(evc4.0) I displayed the following CE Memory
    Statistics:
    - Available Physical (scale = 0.000001)
    - Memory Load (scale = 1.0)
    - Total Physical (scale = 0.000001)

    The main difference I witnessed was with the second example where the
    Bitmap (int, int) constructor was used. Using the WinCE RPM(evc 4.0),
    you’ll quickly see the Available Physical Memory drop 27 – 32MB,
    roughly equivalent to the max mem allowable for the process. I’d
    expect the CLR to clean that up when the application exits, however,
    that memory is "leaked" until the device is hard reset. This is what I
    had been seeing occasionally with one app I have been working on.

    While reading the response from the CF team concerning DIBs and DDBs, I
    started to wonder what happens with an ImageList, more specifically,
    what happens under the hood with a ImageList? When adding a bitmap to
    an ImageList, is a reference to the original bitmap used or does a new
    bitmap get created??? If a new bitmap is created, which constructor is
    used???

    After a bit of experimenting it seems that the ImageList creates a copy
    of the original Image. Fortunately, it does not seem to use the
    Bitmap(int, int) constructor. I was fearing for the worse given the
    ImageSize property. Exactly what happens under the hood, only MS knows
    for certain? What is certain is that all members of the ImageList
    collection must also be disposed of since it appears new images are
    created.

    I took the liberty to modify some of the code from the blog to
    determine if a reference or a copy of the Bitmap was used in the
    ImageList. The following code snippet runs in a ThreadPool thread,
    hence the raising events & etc.:

    while (loop_run) {
    try {
    iterations++;
    using (Bitmap b = new Bitmap(GetImageStream())) {

    il_5.Images.Add(b);
    }

    RaiseUpdateListViewEvent(obj_since_failure++);

    if (iterations % 100 == 0) {
    Debug.WriteLine(string.Format("{0} objects
    Created", iterations));
    break;
    }
    } catch (OutOfMemoryException) {
    Debug.WriteLine("Waiting for finalizers to
    run…");
    Debug.WriteLine(string.Format("{0} objects since
    last failure", obj_since_failure));
    obj_since_failure = 0;
    ClearImageList(il_5);
    GC.WaitForPendingFinalizers();
    } catch (Exception) {
    Debugger.Break();
    loop_run = false;
    }
    }

    In the example above, the ImageList il_5 is bound to the SmallImageList
    of a ListView object. The RaiseUpdateListView method raises an event
    to create a new ListViewItem and update the Listview. I figured, if
    the ListView displays the Bitmap even after Bitmap b is disposed, then
    the Image must have been copied somewhere. This is indeed what
    happens.

    In addition to this example, I also added the bitmaps created in each
    example of ctacke’s blog to separate ImageLists. With example 1 from
    ctacke’s blog, adding the Bitmap to a ImageList made the "ideal"
    performance dissappear pretty quickly. Fortunately, all resources were
    released when the application exits. All of the other examples behave
    roughly in the same manner. Example number 4 will occasionally throw
    an regular Exception when attempting to add the bitmap to the ImageList
    after memory usage gets past a certain point.(Weird)

    Anyhow, this ballyhoo has shed some light on what one I must do to
    clean up my app which uses a fair amount of Bitmaps, ImageLists, etc.

    Thank you all for the insight.

Leave a Reply