Several months ago we where evaluating image processing libraries for their resize performance. We compared several of them on two platforms: .NET Core 2.1 (Windows) and .NET CLR 4.7 (also Windows). I can present comparison between CoreCompat.System.Drawing.v2 (5.2.0-preview1-r131) and ImageSharp (1.0.0-beta0005):
BenchmarkDotNet=v0.11.1, OS=Windows 10.0.17134.345 (1803/April2018Update/Redstone4)
Intel Core i7-7660U CPU 2.50GHz (Max: 1.70GHz) (Kaby Lake), 1 CPU, 4 logical and 2 physical cores
.NET Core SDK=2.1.403
[Host] : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT
DefaultJob : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT
| Method | Mean | Error | StdDev | Scaled | ScaledSD | Allocated |
|------------------------------------ |---------:|----------:|---------:|-------:|---------:|-----------:|
| System_Drawing_resize_jpg_2520x1575 | 328.6 ms | 7.306 ms | 10.00 ms | 1.00 | 0.00 | 681.54 KB |
| ImageSharp_resize_jpg_2520x1575 | 670.4 ms | 21.978 ms | 64.80 ms | 2.04 | 0.21 | 99074.4 KB |
As I remember ImageSharp was advertised as a new high performance library, yet we approached to it several times in the last half year, and it never stood a chance against System.Drawing or SkiaSharp. Are doing something wrong here?
Here's the code we used for the benchmark:
[Benchmark(Baseline = true)]
public Stream System_Drawing_resize_jpg_2520x1575()
{
using (var image = System.Drawing.Image.FromFile(ImagePath))
{
var width = 2000;
var height = 2000 * image.Height / image.Width;
using (var bmp = new System.Drawing.Bitmap(width, height))
{
bmp.SetResolution(image.HorizontalResolution, image.VerticalResolution);
using (var g = System.Drawing.Graphics.FromImage(bmp))
{
g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
using (var wrap = new System.Drawing.Imaging.ImageAttributes())
{
wrap.SetWrapMode(System.Drawing.Drawing2D.WrapMode.TileFlipXY);
var rect = new System.Drawing.Rectangle(0,0, width, height);
g.DrawImage(image, rect, 0, 0, image.Width, image.Height, System.Drawing.GraphicsUnit.Pixel, wrap);
var stream = new MemoryStream();
bmp.Save(stream, System.Drawing.Imaging.ImageFormat.Jpeg);
return stream;
}
}
}
}
}
[Benchmark]
public Stream ImageSharp_resize_jpg_2520x1575()
{
using (var input = File.OpenRead(ImagePath))
{
var image = SixLabors.ImageSharp.Image.Load(input);
var width = 2000;
var height = 2000 * image.Height / image.Width;
var output = new MemoryStream();
image.Clone(i =>
i.Resize(width, height, KnownResamplers.Bicubic))
.SaveAsJpeg(output);
return output;
}
}
What happens when you use Mutate instead of Clone?
And dispose of your image...
And read #733
@Horusiath I don't think we ever advertised ImageSharp as a "high performance library", I would rather say it's a continously evolving library.
I really hope, that for the final 1.0 release we will be able to close the performance gap compared to System.Drawing, which is a theoretical limit with the current RyuJIT capabilities. The integration of dotnet/corefx#22940 into .NET Core 3.0 / .NET Framework 4.8 will open up new opportunities in the future.
Please note that just about 2 years ago we started from being 10-20x slower, and we achieved these improvements working in our free time. We are investigating the possibilities to find a sustainable business model to fund the project, so we can invest more time into the performance improvements.
And of course the remarks on Mutate and Dispose are valid!
@dlemstra I'm sharing the results after using mutate and disposing the image:
BenchmarkDotNet=v0.11.1, OS=Windows 10.0.17134.345 (1803/April2018Update/Redstone4)
Intel Core i7-7660U CPU 2.50GHz (Max: 1.70GHz) (Kaby Lake), 1 CPU, 4 logical and 2 physical cores
.NET Core SDK=2.1.403
[Host] : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT
DefaultJob : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT
| Method | Mean | Error | StdDev | Median | Scaled | ScaledSD | Allocated |
|------------------------------------ |---------:|----------:|---------:|---------:|-------:|---------:|------------:|
| System_Drawing_resize_jpg_2520x1575 | 329.3 ms | 9.164 ms | 11.59 ms | 327.9 ms | 1.00 | 0.00 | 681.54 KB |
| ImageSharp_resize_jpg_2520x1575 | 600.8 ms | 21.504 ms | 63.41 ms | 633.5 ms | 1.83 | 0.20 | 49922.45 KB |
@antonfirsov Don't worry - I'm heavily contributing to some OSS projects for over 4 years. I know the struggle of that model ;) I hope you guys will find a good path for yourself to provide means for continuous improvement.
Btw. aren't you using SIMD (at least as part of System.Numerics.Vectors) already?
@Horusiath Could you try the latest nightlies please? I'd like to see how the recent improvements fair.
We were using SIMD already in it's limited capacity and @antonfirsov has already added some further improvements utilizing the new Core 2.1 API's both to the resampling algorithm and scanline processing.
System.Numerics.Vectors is a limited subset. We miss operations as basic as shuffle or shifting(<<, >>) .
@Horusiath FYI my benchmark results for a very similar scenario, downscaling and resaving a 2048 x 1536 Jpeg to 512 x 384.
These results are a bit better than yours, here is a speculative explanation:
We probably perform better on high-end CPU-s operating on high frequencies, because this setup is accelerating the effect of Intel's state-of-art AVX2 implementations. It is very likely that the GDI+ Drawing API-s do not utilize the newest SIMD instructions, because by benchmarking only Resize, it looks like we are already faster than System.Drawing.
On the other hand, the GDI+ jpeg codecs beat us because they are powered by WIC as backend.
On the other hand, the GDI+ jpeg codecs beat us because they are powered by WIC as backend.
I'd give my right arm to see the source code. It's about as fast as a jpeg decoder gets.
I'm closing this. We'll open jpeg performance specific issues when they come around.
Most helpful comment
@Horusiath I don't think we ever advertised ImageSharp as a "high performance library", I would rather say it's a continously evolving library.
I really hope, that for the final 1.0 release we will be able to close the performance gap compared to System.Drawing, which is a theoretical limit with the current RyuJIT capabilities. The integration of dotnet/corefx#22940 into .NET Core 3.0 / .NET Framework 4.8 will open up new opportunities in the future.
Please note that just about 2 years ago we started from being 10-20x slower, and we achieved these improvements working in our free time. We are investigating the possibilities to find a sustainable business model to fund the project, so we can invest more time into the performance improvements.