none
Memory usage / fragmentation of Images: Bitmap vs. Metafile RRS feed

  • Question

  • Due to seemingly-premature Out of Memory exceptions, we have been examining closely the memory usage of various .NET constructs... particularly large objects that tend to fragment the Large Object Heap, causing premature Out of Memory exceptions.  One area that has been a bit surprising is the .NET Image classes:  Bitmap and Metafile.

    Here's what we think we have learned, but have been unable to find MS documentation to verify, so we would appreciate any confirmation others can give:

    (1)  When you create a Bitmap object from a compressed raster file (JPG, PNG, GIF, etc), it consumes memory for a fully uncompressed pixel array, at the full resolution of that file.  So, for example, a 5MB JPG that is 9000x3000 pixels would be expanded into 9000x3000x3 bytes (assuming 24bit color, no alpha), or 81MB of memory consumed.  Correct?

    (1a)  There's some evidence (see 2b below) that it ALSO stores the original compressed format... so, actually 86MB in this case.  But that's unclear... does anyone know?

    (2)  When you create a Metafile object and then draw a raster file (JPG, PNG, GIF, etc) into it, it only consumes memory for the compressed file.  So, if you draw a 5MB JPG that is 9000x3000 pixels into a Metafile, it will only consume roughly 5MB of memory.  Correct?

    (2a)  To draw a raster file into a Metafile object, the only way seems to be to load a Bitmap with the file and then draw the Bitmap into the Metafile.  Is there a better way that doesn't involve temporarily loading that huge Bitmap data (and causing the associated memory fragmentation)?

    (2b)  When you draw a Bitmap into a Metafile, it uses a compressed format of size similar to the original compressed file.  Does it do that by storing the original compressed file in the Bitmap?  Or does it do it by re-compressing the expanded Bitmap using the original compression settings?

    (3)  We originally assumed that large (>85KB) Image objects would be placed in the Large Object Heap.  In fact, that seems to NOT be the case.  Rather, each Bitmap and each Metafile is a 24-byte object in the Small Object Heap that refers to a block of Native Memory that contains the real data.  Correct?

    (3a)  We assume such Native Memory is like Large Object Heap in that it cannot be compacted... once the big object is laid into Native Memory, it will never be moved, and thus fragmentation of Native Memory can cause as many problems as fragmentation of Large Object Heap.  True?  Or is there special handling of the underlying Bitmap / Metafile data that is more efficient?

    (3b)  So, there seems to be four independent blocks of memory that are managed separately, and running out of each can result in the same Out of Memory exceptions:  Small Object Heap (managed objects < 85KB, compacted by the GC), Large Object Heap (managed objects > 85KB that are collected by GC, but not compacted), Native Memory (unmanaged objects, presumably not compacted), and Desktop Heap (where windows handles and such limited resources are managed).  Have I documented those four properly?  Are there others we should be aware of?

    Any clarity that anybody can provide on the above would be greatly appreciated.  If there is a good book or article that fully explains the above, please let me know.  (I am happy to do the required reading; but the vast majority of books don't get that deep, and thus don't tell me anything I don't already know.)

    Thanks!

    Wednesday, March 20, 2013 4:33 PM

Answers

  • Hi Tcc Developer,

    Thank you for posting on MSDN Forum.

    For of all, for the out of memory exception, you can check this blog: http://blogs.msdn.com/b/yunjin/archive/2004/01/27/63642.aspx And based on my understanding, your scenario it 2):

    The GC heap itself is fragmented, meaning GC can't allocate objects in already reserved segments which actually have enough free space inside.

    >>(1)

    The bitmap is not decompressed from the Image you provided, it create a new object, and redraw the image into a bitmap with specific size, as you know, bitmap is consisted of the pixel data for a graphics image and its attributes.

    So for a 9000*3000 pixels image, it size will be 9000x3000x3 bytes . In all, I agree with you at this point.

    >>(1a)

    No, bitmap doesn't store the original image data. To confirm this, you can also try Windbg to check the size of bitmap variable: !objsize http://blogs.msdn.com/b/johan/archive/2007/11/26/getting-started-with-windbg-part-ii.aspx   

    >>(2)  if you draw a 5MB JPG that is 9000x3000 pixels into a Metafile,it will only consume roughly 5MB of memory.  Correct?

    This depends how you use the image, when you declare an Image object with this file, it still consume 81MB memory. Since there is a related bitmap has been generated.

    >>(2b)

    It doesn't store the original image compression settings. It depends on the format you specificed at the save method.

    >>(3)

    Simply answer, Yes. 

    >>(3a) it will never be moved

    No, when you code the dispose method, it will be cleaned.

    >>and thus fragmentation of Native Memory can cause as many problems as fragmentation of Large Object Heap.  True?

    What kind of problems do you mean?

    >>(3b)

    As far as I know, yes, it is all.

    And generally, we care about the first two.

    Anything unclear, please feel free to follow up.

    Thanks.


    Mike Feng
    MSDN Community Support | Feedback to us
    Develop and promote your apps in Windows Store
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Tuesday, March 26, 2013 3:57 PM
    Moderator
  • >>(1a)

    No, bitmap doesn't store the original image data. To confirm this, you can also try Windbg to check the size of bitmap variable: !objsize http://blogs.msdn.com/b/johan/archive/2007/11/26/getting-started-with-windbg-part-ii.aspx   

    >>(2)  if you draw a 5MB JPG that is 9000x3000 pixels into a Metafile,it will only consume roughly 5MB of memory.  Correct?

    This depends how you use the image, when you declare an Image object with this file, it still consume 81MB memory. Since there is a related bitmap has been generated.

    >>(2b)

    It doesn't store the original image compression settings. It depends on the format you specificed at the save method.

    After much testing and analysis, we are pretty certain that:

    (1a / 2b)  If you load a Bitmap from a compressed file or stream, it does keep that data around... you can see it maintained in a Memory Profiler... and you can see that the size of a Metafile drawn using that Bitmap object has a copy of that original file or stream data.  If you generate the identical Bitmap via other means (such that there is no associated file or stream), then create a Metafile from that Bitmap the same way, you end up with a Metafile that contains the uncompressed pixel array.

    (2)  If you construct a Metafile containing a JPG, you can then draw that Metafile into things without ever generating the uncompressed pixel array in memory.  Obviously you will generate it into the screen memory, but that memory is always consumed anyway.  You can even do things like draw a rectangle subset of the JPG, scaled down into another small Bitmap, without ever generating the full uncompressed pixel array... just the new small subset pixel array.  It is a little slow doing that, but no memory fragmentation caused.

    If anyone knows anything to the contrary, please let us know.

    • Marked as answer by TCC Developer Wednesday, April 3, 2013 4:54 PM
    Wednesday, April 3, 2013 4:54 PM
  • If you go one step farther and construct your images using the Image.Fromstream(FileStream, Boolean, false) overload no managed memory will be used and it will render much more rapidly than a metafile.


    Ahhh... I missed that the third parameter, which says nothing about not loading up the Bitmap the same way, actually dictates whether or not the uncompressed pixel array gets loaded up... thanks for pointing that out, JohnWein!

    So, with that, the answer to 2a is this code:

            /// <summary>
            /// Returns a new EMF+ Metafile that contains just one drawing command:  an image loaded from filestram using the argument path.
            /// </summary>
            /// <param name="imageFilePath">The file path to the Image to be the sole object drawn in the Metafile.</param>
            public static Metafile NewMetafileContaining(string imageFilePath)
            {
                Metafile mf = NewMetafile();
                using (FileStream fs = new FileStream(imageFilePath, FileMode.Open))
                using (Image image = Image.FromStream(fs, false, false)) // false's avoid loading pixel array
                using (Graphics gmf = Graphics.FromImage(mf))
                {
                    gmf.DrawImage(image, new Rectangle(0, 0, image.Width, image.Height));
                }
                return mf;
            }
    

    where we use this function to create a new Metafile ready to be drawn into:

            /// <summary>
            /// Returns a new empty EMF+ Metafile recording to an inaccessible memory position.
            /// You will only be able to get the Graphics for this Metafile once to draw on it.
            /// And you will only be able to use the resulting Metafile to draw into other things.
            /// If you want to be able to store the Metafile or write it out, pass in a 'stream' argument.
            /// Note that this Metafile should not be used until first you get a Graphics and close it;
            /// until then, you will get "Parameter not valid." Exceptions if you try to use it.
            /// </summary>
            public static Metafile NewMetafile()
            {
                using (Graphics g = Graphics.FromHwnd(IntPtr.Zero))  // offscreen device context
                {
                    IntPtr hdc = g.GetHdc(); // gets released by g.Dispose() called by using g
                    return new Metafile(hdc, EmfType.EmfPlusOnly);
                }
            }
    
    

    That seems to do what we want... thanks!

    • Marked as answer by TCC Developer Thursday, April 4, 2013 9:10 PM
    Thursday, April 4, 2013 9:10 PM

All replies

  • I will check it later.

    Ghost,
    Call me ghost for short, Thanks
    To get the better answer, it should be a better question.

    Thursday, March 21, 2013 8:56 AM
  • Hi Tcc Developer,

    Thank you for posting on MSDN Forum.

    For of all, for the out of memory exception, you can check this blog: http://blogs.msdn.com/b/yunjin/archive/2004/01/27/63642.aspx And based on my understanding, your scenario it 2):

    The GC heap itself is fragmented, meaning GC can't allocate objects in already reserved segments which actually have enough free space inside.

    >>(1)

    The bitmap is not decompressed from the Image you provided, it create a new object, and redraw the image into a bitmap with specific size, as you know, bitmap is consisted of the pixel data for a graphics image and its attributes.

    So for a 9000*3000 pixels image, it size will be 9000x3000x3 bytes . In all, I agree with you at this point.

    >>(1a)

    No, bitmap doesn't store the original image data. To confirm this, you can also try Windbg to check the size of bitmap variable: !objsize http://blogs.msdn.com/b/johan/archive/2007/11/26/getting-started-with-windbg-part-ii.aspx   

    >>(2)  if you draw a 5MB JPG that is 9000x3000 pixels into a Metafile,it will only consume roughly 5MB of memory.  Correct?

    This depends how you use the image, when you declare an Image object with this file, it still consume 81MB memory. Since there is a related bitmap has been generated.

    >>(2b)

    It doesn't store the original image compression settings. It depends on the format you specificed at the save method.

    >>(3)

    Simply answer, Yes. 

    >>(3a) it will never be moved

    No, when you code the dispose method, it will be cleaned.

    >>and thus fragmentation of Native Memory can cause as many problems as fragmentation of Large Object Heap.  True?

    What kind of problems do you mean?

    >>(3b)

    As far as I know, yes, it is all.

    And generally, we care about the first two.

    Anything unclear, please feel free to follow up.

    Thanks.


    Mike Feng
    MSDN Community Support | Feedback to us
    Develop and promote your apps in Windows Store
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Tuesday, March 26, 2013 3:57 PM
    Moderator
  • >>(1a)

    No, bitmap doesn't store the original image data. To confirm this, you can also try Windbg to check the size of bitmap variable: !objsize http://blogs.msdn.com/b/johan/archive/2007/11/26/getting-started-with-windbg-part-ii.aspx   

    >>(2)  if you draw a 5MB JPG that is 9000x3000 pixels into a Metafile,it will only consume roughly 5MB of memory.  Correct?

    This depends how you use the image, when you declare an Image object with this file, it still consume 81MB memory. Since there is a related bitmap has been generated.

    >>(2b)

    It doesn't store the original image compression settings. It depends on the format you specificed at the save method.

    After much testing and analysis, we are pretty certain that:

    (1a / 2b)  If you load a Bitmap from a compressed file or stream, it does keep that data around... you can see it maintained in a Memory Profiler... and you can see that the size of a Metafile drawn using that Bitmap object has a copy of that original file or stream data.  If you generate the identical Bitmap via other means (such that there is no associated file or stream), then create a Metafile from that Bitmap the same way, you end up with a Metafile that contains the uncompressed pixel array.

    (2)  If you construct a Metafile containing a JPG, you can then draw that Metafile into things without ever generating the uncompressed pixel array in memory.  Obviously you will generate it into the screen memory, but that memory is always consumed anyway.  You can even do things like draw a rectangle subset of the JPG, scaled down into another small Bitmap, without ever generating the full uncompressed pixel array... just the new small subset pixel array.  It is a little slow doing that, but no memory fragmentation caused.

    If anyone knows anything to the contrary, please let us know.

    • Marked as answer by TCC Developer Wednesday, April 3, 2013 4:54 PM
    Wednesday, April 3, 2013 4:54 PM
  • "If anyone knows anything to the contrary, please let us know."

    If you go one step farther and construct your images using the Image.Fromstream(FileStream, Boolean, false) overload no managed memory will be used and it will render much more rapidly than a metafile.


    • Edited by JohnWein Thursday, April 4, 2013 6:33 AM
    Wednesday, April 3, 2013 7:56 PM
  • If you go one step farther and construct your images using the Image.Fromstream(FileStream, Boolean, false) overload no managed memory will be used and it will render much more rapidly than a metafile.


    Ahhh... I missed that the third parameter, which says nothing about not loading up the Bitmap the same way, actually dictates whether or not the uncompressed pixel array gets loaded up... thanks for pointing that out, JohnWein!

    So, with that, the answer to 2a is this code:

            /// <summary>
            /// Returns a new EMF+ Metafile that contains just one drawing command:  an image loaded from filestram using the argument path.
            /// </summary>
            /// <param name="imageFilePath">The file path to the Image to be the sole object drawn in the Metafile.</param>
            public static Metafile NewMetafileContaining(string imageFilePath)
            {
                Metafile mf = NewMetafile();
                using (FileStream fs = new FileStream(imageFilePath, FileMode.Open))
                using (Image image = Image.FromStream(fs, false, false)) // false's avoid loading pixel array
                using (Graphics gmf = Graphics.FromImage(mf))
                {
                    gmf.DrawImage(image, new Rectangle(0, 0, image.Width, image.Height));
                }
                return mf;
            }
    

    where we use this function to create a new Metafile ready to be drawn into:

            /// <summary>
            /// Returns a new empty EMF+ Metafile recording to an inaccessible memory position.
            /// You will only be able to get the Graphics for this Metafile once to draw on it.
            /// And you will only be able to use the resulting Metafile to draw into other things.
            /// If you want to be able to store the Metafile or write it out, pass in a 'stream' argument.
            /// Note that this Metafile should not be used until first you get a Graphics and close it;
            /// until then, you will get "Parameter not valid." Exceptions if you try to use it.
            /// </summary>
            public static Metafile NewMetafile()
            {
                using (Graphics g = Graphics.FromHwnd(IntPtr.Zero))  // offscreen device context
                {
                    IntPtr hdc = g.GetHdc(); // gets released by g.Dispose() called by using g
                    return new Metafile(hdc, EmfType.EmfPlusOnly);
                }
            }
    
    

    That seems to do what we want... thanks!

    • Marked as answer by TCC Developer Thursday, April 4, 2013 9:10 PM
    Thursday, April 4, 2013 9:10 PM
  • So now that you seem to finally understand the use of Image.FromStream without validation, what's the purpose of the metafile.  It increases your memory usage and slows down rendering of the image.  Also, once it's closed, it's a major operation to do any additional graphics operations.
    Thursday, April 4, 2013 9:23 PM
  • So now that you seem to finally understand the use of Image.FromStream without validation, what's the purpose of the metafile.  It increases your memory usage and slows down rendering of the image.  Also, once it's closed, it's a major operation to do any additional graphics operations.

    We are loading the image from a Database / DataSet Image column right now... databinding a PictureBox to it.  So, the easy change is to put a Metafile containing a JPG into that... we can use it in memory as we always have... no uncompressed pixel array.

    The next improvement would be to pull that out of the Database / DataSet Image column and start keeping all images in Files.  Then we would need to stream the files from server to temp files on the client... then we can open those temp files this way ... avoiding the uncompressed pixel array AND the compressed JPG consuming memory.  But that will be a HUGE change that we can't do in a patch release... so, that's an optimization for a future release cycle.

    But even after we do that, there are other places where we will want to be able to use the Metafile trick rather than using temp files.

    Thursday, April 4, 2013 9:38 PM
  • Usually an image is held in a database as a byte[] containing a compressed file.  You would show it in a PictureBox using a MemoryStream constructed from the byte[].  Same memory as the metafile, but better rendering performance.  For some reason a metafile containing a JPEG is slow to render.

    Anyway, either Image.FromStream or Metafile is much better for the simple display of images than Bitmap with its full uncompressed image.  Nothing destroys an app for a user quicker than extensive page file usage. 


    • Edited by JohnWein Thursday, April 4, 2013 10:15 PM
    Thursday, April 4, 2013 10:14 PM