Skip to content

jpg.decodedBlock should it release memory after usage? #224

Description

@mhamri

Prerequisites

  • I have written a descriptive issue title
  • I have verified that I am running the latest version of ImageSharp
  • I have verified if the problem exist in both DEBUG and RELEASE mode
  • I have searched open and closed issues to ensure it has not already been reported

Description

I was using imagesharp 1 alpha 4 48 previousely and my app for a normal task took up to 4gb, today I updated to latest version and saw it decreased to 3gb. thanks for the improvement.

I just ran a profiler and saw that the type of decoded block and color block from static type changed to normal property so I got some concern if i'm doing something wrong. I assume that it should release the resources when I call the dispose method or it reach end of the using statement.

I checked almost all of my code, i have using and dispose for every usage. also the class that wrapp the imagesharp is implementing IDisposable.

2017-05-24 18_49_26-pbww imageserver running - microsoft visual studio

Steps to Reproduce

i'm using imagesharp with DI in startup.cs, configServices method:

services.AddTransient<ImageSharpHelper>();

and the imageSharpHelper is like bellow:

using ImageSharp;
using ImageSharp.Formats;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.IO;

namespace namespace1.namespace2.Image
{
    public class ImageSharpHelper : IDisposable
    {
        private readonly ILogger<ImageSharpHelper> _logger;

        public ImageSharpHelper(ILogger<ImageSharpHelper> logger)
        {
            _logger = logger;
        }

        public Image<Rgba32> Client { get; set; }
        public int? CompressRate { get; set; }
        public string Mime { get; set; }
        public ImageOutPutTypes OutPutTypes { get; set; }
        public int? QualityRate { get; set; }

        private Dictionary<ImageOutPutTypes, Func<MemoryStream, string>> SaveImageMapper => new Dictionary<ImageOutPutTypes, Func<MemoryStream, string>>()
        {
            [ImageOutPutTypes.Bmp] = SaveAsBmp,
            [ImageOutPutTypes.Jpg] = SaveAsJpg,
            [ImageOutPutTypes.Png] = SaveAsPng,
            [ImageOutPutTypes.None] = SaveImage
        };

        public void Compress(int compress, int quality)
        {
            CompressRate = compress;
            QualityRate = quality;
        }

        public void Crop(int? x, int? y, int? width, int? height)
        {
            if (x == null || y == null || width == null || height == null)
            {
                throw new NullReferenceException("Crop- Neither Width or height or X or Y can be null");
            }
            _logger.LogDebug($"croping with x:{x} y:{y} width:{width} height:{height}");

            var rectangle = new Rectangle((int)x, (int)y, (int)width, (int)height);
            Client.Crop(rectangle);
        }

        public void Resize(int? width, int? height)
        {
            if (width == null || height == null)
            {
                throw new NullReferenceException("Resize- Neither Width or height can be null");
            }

            _logger.LogDebug($"croping width:{width} - height:{height}");

            Client.Resize((int)width, (int)height);
        }

        public Stream SaveAs()
        {
            var fileStream = new MemoryStream();
            Func<MemoryStream, string> saveOutputMethod;

            if (SaveImageMapper.TryGetValue(OutPutTypes, out saveOutputMethod))
            {
                Mime = saveOutputMethod(fileStream);
            }
            else
            {
                Mime = SaveImage(fileStream);
            }
            return fileStream;
        }

        public void SetImage(Stream file)
        {
            Client?.Dispose();
            Client = ImageSharp.Image.Load<Rgba32>(file);
            Mime = Client.CurrentImageFormat.MimeType;
            OutPutTypes = ImageOutPutTypes.None;
        }
        private string SaveAsBmp(MemoryStream fileStream)
        {
            Client.SaveAsBmp(fileStream);
            return "image/bmp";
        }

        private string SaveAsJpg(MemoryStream fileStream)
        {
            var jpegEncoderOptions = new JpegEncoderOptions()
            {
                IgnoreMetadata = true,
            };

            if (QualityRate != null)
            {
                jpegEncoderOptions.Quality = (int)QualityRate;
            }
            Client.SaveAsJpeg(fileStream, jpegEncoderOptions);
            return "image/jpg";
        }

        private string SaveAsPng(MemoryStream fileStream)
        {
            var pngEncoderOptions = new PngEncoderOptions()
            {
                //Quantizer = new OctreeQuantizer<Color>() {/*Dither = false*/},
                IgnoreMetadata = true
            };

            if (QualityRate != null)
            {
                pngEncoderOptions.Quality = (int)QualityRate;
            }
            if (CompressRate != null)
            {
                pngEncoderOptions.CompressionLevel = (int)CompressRate;
            }
            Client.SaveAsPng(fileStream, pngEncoderOptions);
            return "image/png";
        }

        private string SaveImage(MemoryStream fileStream)
        {
            Client.Save(fileStream);
            return Client.CurrentImageFormat.MimeType;
        }

        public void Dispose()
        {
            Client?.Dispose();
        }
    }
}

and i'm calling the class like bellow:

using (var imageSharpHelper = _serviceProvider.GetService<ImageSharpHelper>())
            {
                imageSharpHelper.SetImage(latestAvailableVersion.Image);
                imageSharpHelper.Resize(x, y);
                return imageSharpHelper.SaveAs();
            }

System Configuration

  • ImageSharp version: 1.0.0-alpha9-00058
  • Other ImageSharp packages and versions:
  • Environment (Operating system, version and so on): windows 10
  • .NET Framework version: asp.net core 1.1.1
  • Additional information:

Activity

  1. antonfirsov commented on May 25, 2017

    @antonfirsov
    Member

    @mhamri it looks like duplicate of #151, but thanks for the detailed description of your use-case!

    The memory usage drop from 4GB to 3GB is random, we haven't done actual improvements on this. I think it depends on the input images your service is processing. Do your inputs have a common characteristic? (eg: large photos). If yes, it might be helpful to post a few of them.

    Implementing #225 and certain points of #192 should help on this. Unfortunately both are big tasks, so we need your patience here.

  2. mhamri commented on May 25, 2017

    @mhamri
    Author

    well, about the 3gb to 4gb, i'm sure there was some changes, i have a batch of 40 images in one xml file that generates 4gb memory usage. i ran this test file almost 50 times, even before i do update to latest package. after update to the latest package, the usage decreased to 3gb.

    i'm not sure it's same with #151, because my question is why the referenced object is not releasing memory if i'm disposing the object correctly? what is keeping a reference to those color and decoder array? I understand the previous memory usage when the decoder and color array was static. but now that is not static why still a reference is kept somewhere to those arrays? you got what do I mean?

  3. antonfirsov commented on May 25, 2017

    @antonfirsov
    Member

    It is ArrayPool. Pools large arrays in a shared (static) pool to reduce gc pressure and prevent LOH issues. We understand this is not the best behavior for many users though, so we want to make this configurable.

  4. mhamri commented on May 25, 2017

    @mhamri
    Author
  5. antonfirsov commented on May 25, 2017

    @antonfirsov
    Member

    There might be an unexpected behavior (missing dispose calls?) that came with #210. Looks like we need to do a deeper investigation here. @JimBobSquarePants I think we really need to find a way to stress test the library with a larger test image pack.

  6. JimBobSquarePants commented on May 25, 2017

    @JimBobSquarePants
    Member

    Agreed, we do.

  7. mhamri commented on May 26, 2017

    @mhamri
    Author

    I hope this help, it's my set of test images, all are stock photos, good quality, and high dpi and pixel

    url

  8. JimBobSquarePants commented on May 26, 2017

    @JimBobSquarePants
    Member

    Thanks! Really appreciate it.

  9. antonfirsov commented on Sep 16, 2017

    @antonfirsov
    Member

    @mhamri If you are still using ImageSharp, check out the beta, the Jpeg decoder uses much less memory now! Let me know if the situation is still critical for you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions