0

I need to use memory stream because I load image from a database. When I use file stream, just to compare, footprint of the app is consistent with 1x size of uncompressed image. But if I use memory stream, memory usage is more like 3x. I used 200MB of png images (4 images) and the app took 1GB of ram.

Is there a way to reduce memory usage?

private BitmapImage readfromdsk(string s)
        {

            var photo2f = File.Open(s, FileMode.Open, FileAccess.Read);

            byte[] b = null;
            photo2f.Read(b=new byte[photo2f.Length], 0, (int)photo2f.Length);

            var bitmapImage = new BitmapImage();

            using (MemoryStream ms = new MemoryStream(b))
            {

                bitmapImage.BeginInit();
                bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
                bitmapImage.StreamSource = ms;   //// lot of memory allocated on this line
                bitmapImage.EndInit();
            }

            b = null;
            photo2f.Dispose();

            return bitmapImage;
        }

2 Answers2

0

What's happening here is that BitmapImage is creating its own copy of the pixels that it owns and manages.

In the file stream version, it can read the file in small blocks (e.g. 4kb at a time). So, in this example, it will wind up using (4kb + uncompressed bitmap size) memory.

In your memory stream version, you first read the entire file into memory. So it winds up being (file size + uncompressed bitmap size). BitmapImage doesn't use your byte array directly. It still "streams" it a chunk at a time.

If you want to reduce memory when reading form you DB, you'd need to build a streaming class (that implements System.IO.Stream) that reads from your database a chunk at a time.

Last thing to consider: This may or may not be an issue at all. The GC will recover the memory used by the byte array, so the increase in memory usage is only temporary.

Oz Solomon
  • 2,969
  • 23
  • 22
  • I use visual studio performance profiler, gc does nothing. Also everything should be cleared (except bitmapImage.StreamSource because I use cache). File stream , memory stream and bytes are all gone. Also I will look into streaming class, thanks. – user3761570 Jul 12 '23 at 20:04
  • You forgot that stream need to grow first when it filled up - that will properly match OP's observations of 3x (stream's memory usage is roughly 1.5-2x of the data size, and bitmaps itself is 1x resulting in at least 2.5-3x size range of allocated virtual memory that OP is likely looking at... ). – Alexei Levenkov Jul 12 '23 at 21:58
  • Alexei, great point, I'll amend the answer. – Oz Solomon Jul 13 '23 at 22:38
  • Actually, upon checking, it looks like the memory stream uses the byte array directly instead of copying to its own, so I'm not sure this is a contributing factor. See https://stackoverflow.com/questions/52482122/does-memorystream-copy-on-construction – Oz Solomon Jul 13 '23 at 22:41
  • @OzSolomon For your understanding, the actual problem is that a MemoryStream never releases its reference to the byte array that you pass on construction, and the BitmapImage keeps a reference to the MemoryStream. – Clemens Jul 14 '23 at 10:56
0

As pointed out in

.NET Memory issues loading ~40 images, memory not reclaimed, potentially due to LOH fragmentation

a MemoryStream seems to keep a reference to the byte array that you pass on construction even after it was disposed of. This is a problem because a BitmapImage also keeps a reference to its StreamSource, so the original input byte array is never released.

To avoid this, you can use an additional BufferedStream, which releases the reference to its source stream when it is disposed of.

public BitmapSource LoadBitmap(MemoryStream memoryStream)
{
    using (var bufferedStream = new BufferedStream(memoryStream))
    {
        var bitmap = new BitmapImage();
        bitmap.BeginInit();
        bitmap.CacheOption = BitmapCacheOption.OnLoad;
        bitmap.StreamSource = bufferedStream;
        bitmap.EndInit();
        bitmap.Freeze();
        return bitmap;
    }
}

public BitmapSource LoadBitmap(string path)
{
    var buffer = File.ReadAllBytes(path);

    using (var memoryStream = new MemoryStream(buffer))
    {
        return LoadBitmap(memoryStream);
    }
}
Clemens
  • 123,504
  • 12
  • 155
  • 268