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

Usage "using" syntax with Image objects in Net-vips #114

Closed
AndrePScope opened this issue Feb 3, 2021 · 3 comments
Closed

Usage "using" syntax with Image objects in Net-vips #114

AndrePScope opened this issue Feb 3, 2021 · 3 comments
Labels
question Further information is requested

Comments

@AndrePScope
Copy link

AndrePScope commented Feb 3, 2021

Hi Net-vips!

Our project has just started with Netvips (libvips) and I faced with the question about disposing of resources.
Every Image object is a SafeHandle object that supports interface IDisposable.
This assumes the preferable way for this is disposing of resources in "using" syntax or directly call Dispose method.

For example, I have code like this:

		public static Image NewTextOverlay_1(string textOverlay,
			string fontName, int fontSize, LengthMeasure fontSizeMeasure,
			Color fontColor, double fontTransparency,
			int canvasWidth, int canvasHeight, string compassDirection)
		{
			var textImage = Image
				.Text(textOverlay, $"{fontName} {fontSize}px", canvasWidth, canvasHeight, dpi: 500)
				.Gravity(compassDirection, canvasWidth, canvasHeight);

			var alpha = textImage * fontTransparency;
			return textImage
				.NewFromImage(fontColor.R, fontColor.G, fontColor.B)
				.Copy(interpretation: Enums.Interpretation.Srgb)
				.Bandjoin(alpha)
				.Cast(Enums.BandFormat.Uchar);
		}

But now this code should look like this:

		public static Image NewTextOverlay_2(string textOverlay,
			string fontName, int fontSize, LengthMeasure fontSizeMeasure,
			Color fontColor, double fontTransparency,
			int canvasWidth, int canvasHeight, string compassDirection)
		{
			using var textImage = Image.Text(textOverlay, $"{fontName} {fontSize}px", canvasWidth, canvasHeight, dpi: 500);
			using var textImageGr = textImage.Gravity(compassDirection, canvasWidth, canvasHeight);

			using var alpha = textImage * fontTransparency;
			using var newFromImage = textImageGr.NewFromImage(fontColor.R, fontColor.G, fontColor.B);
			using var interpolated = newFromImage.Copy(interpretation: Enums.Interpretation.Srgb);
			using var banded = interpolated.Bandjoin(alpha);

			return banded.Cast(Enums.BandFormat.Uchar);
		}

Indeed, the second method does not look good... But maybe this is preferable for memory?

However, I noticed that you do not use "using" syntax anywhere in your examples and you do not call Dispose method also...

Does this mean that all Image (SafeHandle) objects are very insignificant, and we should not worry about disposing of resources and leave all to the GC?

What code do you recommend (first or second code that I listed)?
Should I care about disposing of resources?

@kleisauke kleisauke added the question Further information is requested label Feb 24, 2021
kleisauke added a commit that referenced this issue Feb 24, 2021
With the exception of the unit tests, as these are for internal use only.
@kleisauke
Copy link
Owner

kleisauke commented Feb 24, 2021

Although the first method seems cleaner, I can recommend the second method for performance-critical applications, as it causes the (temporary-)images to be disposed early instead of having the GC do the cleaning. The reason why this is needed is that CLR has a generational-based GC, which has the disadvantage of not always releasing the memory as soon as possible (see e.g. #96 as a real-world example). AFAIK, reference-counted garbage collectors (like Python) do not have these issues.

There's a libvips thing to track and debug reference counts, you could try enabling that and see if you have any leaks. It can be enabled with the VIPS_LEAK environment variable or by setting NetVips.Leak = true; within NetVips. For example, consider this example program:

Details
public static Image NewTextOverlay_1(string textOverlay,
    string fontName, int fontSize, Color fontColor, double fontTransparency,
    int canvasWidth, int canvasHeight, Enums.CompassDirection compassDirection)
{
    var textImage = Image
        .Text(textOverlay, $"{fontName} {fontSize}px", canvasWidth, canvasHeight, dpi: 500)
        .Gravity(compassDirection, canvasWidth, canvasHeight);

    var alpha = textImage * fontTransparency;
    return textImage
        .NewFromImage(fontColor.R, fontColor.G, fontColor.B)
        .Copy(interpretation: Enums.Interpretation.Srgb)
        .Bandjoin(alpha)
        .Cast(Enums.BandFormat.Uchar);
}

public static Image NewTextOverlay_2(string textOverlay,
    string fontName, int fontSize, Color fontColor, double fontTransparency,
    int canvasWidth, int canvasHeight, Enums.CompassDirection compassDirection)
{
    using var textImage =
        Image.Text(textOverlay, $"{fontName} {fontSize}px", canvasWidth, canvasHeight, dpi: 500);
    using var textImageGr = textImage.Gravity(compassDirection, canvasWidth, canvasHeight);

    using var alpha = textImage * fontTransparency;
    using var newFromImage = textImageGr.NewFromImage(fontColor.R, fontColor.G, fontColor.B);
    using var interpolated = newFromImage.Copy(interpretation: Enums.Interpretation.Srgb);
    using var banded = interpolated.Bandjoin(alpha);

    return banded.Cast(Enums.BandFormat.Uchar);
}

static void Main(string[] args)
{
    // NetVips >= 2.0.0
    NetVips.Leak = true;
    // NetVips < 2.0.0
    //NetVips.LeakSet(true);

    using var im = NewTextOverlay_1("test", "Arial", 36, Color.Red, 0.3, 100, 100,
        Enums.CompassDirection.Centre);

    // using var im = NewTextOverlay_2("test", "Arial", 36, Color.Red, 0.3, 100, 100,
    //     Enums.CompassDirection.Centre);
}

If you run this, you will see that ~42 objects are still alive, because vips_shutdown() is called before the GC cycle kicks in. When using NewTextOverlay_2 instead, you'll notice that the program exits cleanly without any leaks.

However, I noticed that you do not use "using" syntax anywhere in your examples and you do not call Dispose method also.

Indeed, it seems that I relied on the GC to do the cleanup in many examples, ouch. Hopefully fixed with commit c021dac, thanks for noticing this!

@kleisauke
Copy link
Owner

NetVips v2.0.0 is now available with the above fix included. The documentation has been updated to reflect this:
https://kleisauke.github.io/net-vips/introduction.html#overloads

@AndrePScope
Copy link
Author

Thank you for the explanation and fixing documentation.

As I understand you also fixed some places in the internal NetVips code according to correct disposing of objects.
We work on a big project where performance and resource usage is critically important.
Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants