Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
4 tasks done
mhamri opened this issue May 25, 2017 · 9 comments
Closed
4 tasks done

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

mhamri opened this issue May 25, 2017 · 9 comments

Comments

@mhamri
Copy link

mhamri commented May 25, 2017

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:
@antonfirsov
Copy link
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.

@mhamri
Copy link
Author

mhamri commented May 25, 2017

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?

@antonfirsov
Copy link
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.

@mhamri
Copy link
Author

mhamri commented May 25, 2017 via email

@antonfirsov
Copy link
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.

@JimBobSquarePants
Copy link
Member

Agreed, we do.

@mhamri
Copy link
Author

mhamri commented May 26, 2017

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

url

@JimBobSquarePants
Copy link
Member

Thanks! Really appreciate it.

@antonfirsov
Copy link
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
Projects
None yet
Development

No branches or pull requests

3 participants