From f37b46e56760fda5b761bf0e7cdd6404d0e66e12 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 4 Jul 2021 13:23:39 +0200 Subject: [PATCH 01/11] initial import of beeees stress code --- tests/Directory.Build.targets | 7 + .../ImageSharp.Benchmarks.csproj | 8 +- .../LoadResizeSaveStressRunner.cs | 221 ++++++++++++++++++ .../LoadResizeSaveStress_NonParallel.cs | 52 +++++ .../LoadResizeSaveStress_Parallel.cs | 48 ++++ .../ImageSharp.Tests.ProfilingSandbox.csproj | 2 + .../LoadResizeSaveParallelMemoryStress.cs | 100 ++++++++ .../Program.cs | 4 +- 8 files changed, 440 insertions(+), 2 deletions(-) create mode 100644 tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs create mode 100644 tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStress_NonParallel.cs create mode 100644 tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStress_Parallel.cs create mode 100644 tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs diff --git a/tests/Directory.Build.targets b/tests/Directory.Build.targets index 9c17881452..5ca7d2b937 100644 --- a/tests/Directory.Build.targets +++ b/tests/Directory.Build.targets @@ -29,6 +29,13 @@ + + + + + + + diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj index 17f6068d40..30fbbbda98 100644 --- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj +++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj @@ -9,7 +9,7 @@ false Debug;Release;Debug-InnerLoop;Release-InnerLoop - + 9 @@ -41,6 +41,12 @@ + + + + + + diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs new file mode 100644 index 0000000000..77585213fd --- /dev/null +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs @@ -0,0 +1,221 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using FreeImageAPI; +using ImageMagick; +using PhotoSauce.MagicScaler; +using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Tests; +using SkiaSharp; +using ImageSharpImage = SixLabors.ImageSharp.Image; +using ImageSharpSize = SixLabors.ImageSharp.Size; +using NetVipsImage = NetVips.Image; +using SystemDrawingImage = System.Drawing.Image; + +namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave +{ + public class LoadResizeSaveStressRunner + { + private const int ThumbnailSize = 150; + private const int Quality = 75; + private const string ImageSharp = nameof(ImageSharp); + private const string SystemDrawing = nameof(SystemDrawing); + private const string MagickNET = nameof(MagickNET); + private const string NetVips = nameof(NetVips); + private const string FreeImage = nameof(FreeImage); + private const string MagicScaler = nameof(MagicScaler); + private const string SkiaSharpCanvas = nameof(SkiaSharpCanvas); + private const string SkiaSharpBitmap = nameof(SkiaSharpBitmap); + + // Set the quality for ImagSharp + private readonly JpegEncoder imageSharpJpegEncoder = new () { Quality = Quality }; + private readonly ImageCodecInfo systemDrawingJpegCodec = + ImageCodecInfo.GetImageEncoders().First(codec => codec.FormatID == ImageFormat.Jpeg.Guid); + + public string[] Images { get; private set; } + + private string outputDirectory; + + public int ImageCount { get; set; } = int.MaxValue; + + public void Init() + { + if (RuntimeInformation.OSArchitecture is Architecture.X86 or Architecture.X64) + { + // Workaround ImageMagick issue + OpenCL.IsEnabled = false; + } + + string imageDirectory = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, "MemoryStress"); + if (!Directory.Exists(imageDirectory) || !Directory.EnumerateFiles(imageDirectory).Any()) + { + throw new DirectoryNotFoundException($"Copy stress images to: {imageDirectory}"); + } + + // Get at most this.ImageCount images from there + this.Images = Directory.EnumerateFiles(imageDirectory).Take(this.ImageCount).ToArray(); + + // Create the output directory next to the images directory + this.outputDirectory = TestEnvironment.CreateOutputDirectory("MemoryStress"); + } + + private string OutputPath(string inputPath, string postfix) => + Path.Combine( + this.outputDirectory, + Path.GetFileNameWithoutExtension(inputPath) + "-" + postfix + Path.GetExtension(inputPath)); + + private (int width, int height) ScaledSize(int inWidth, int inHeight, int outSize) + { + int width, height; + if (inWidth > inHeight) + { + width = outSize; + height = (int)Math.Round(inHeight * outSize / (double)inWidth); + } + else + { + width = (int)Math.Round(inWidth * outSize / (double)inHeight); + height = outSize; + } + + return (width, height); + } + + public void SystemDrawingResize(string input) + { + using var image = SystemDrawingImage.FromFile(input, true); + (int width, int height) scaled = this.ScaledSize(image.Width, image.Height, ThumbnailSize); + var resized = new Bitmap(scaled.width, scaled.height); + using var graphics = Graphics.FromImage(resized); + using var attributes = new ImageAttributes(); + attributes.SetWrapMode(WrapMode.TileFlipXY); + graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + graphics.CompositingMode = CompositingMode.SourceCopy; + graphics.CompositingQuality = CompositingQuality.AssumeLinear; + graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + graphics.DrawImage(image, System.Drawing.Rectangle.FromLTRB(0, 0, resized.Width, resized.Height), 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, attributes); + + // Save the results + using var encoderParams = new EncoderParameters(1); + using var qualityParam = new EncoderParameter(Encoder.Quality, (long)Quality); + encoderParams.Param[0] = qualityParam; + resized.Save(this.OutputPath(input, SystemDrawing), this.systemDrawingJpegCodec, encoderParams); + } + + public void ImageSharpResize(string input) + { + using FileStream output = File.Open(this.OutputPath(input, ImageSharp), FileMode.Create); + + // Resize it to fit a 150x150 square + using var image = ImageSharpImage.Load(input); + image.Mutate(i => i.Resize(new ResizeOptions + { + Size = new ImageSharpSize(ThumbnailSize, ThumbnailSize), + Mode = ResizeMode.Max + })); + + // Reduce the size of the file + image.Metadata.ExifProfile = null; + + // Save the results + image.Save(output, this.imageSharpJpegEncoder); + } + + public void MagickResize(string input) + { + using var image = new MagickImage(input); + + // Resize it to fit a 150x150 square + image.Resize(ThumbnailSize, ThumbnailSize); + + // Reduce the size of the file + image.Strip(); + + // Set the quality + image.Quality = Quality; + + // Save the results + image.Write(this.OutputPath(input, MagickNET)); + } + + public void FreeImageResize(string input) + { + using var original = FreeImageBitmap.FromFile(input); + (int width, int height) scaled = this.ScaledSize(original.Width, original.Height, ThumbnailSize); + var resized = new FreeImageBitmap(original, scaled.width, scaled.height); + + // JPEG_QUALITYGOOD is 75 JPEG. + // JPEG_BASELINE strips metadata (EXIF, etc.) + resized.Save( + this.OutputPath(input, FreeImage), + FREE_IMAGE_FORMAT.FIF_JPEG, + FREE_IMAGE_SAVE_FLAGS.JPEG_QUALITYGOOD | FREE_IMAGE_SAVE_FLAGS.JPEG_BASELINE); + } + + public void MagicScalerResize(string input) + { + var settings = new ProcessImageSettings() + { + Width = ThumbnailSize, + Height = ThumbnailSize, + ResizeMode = CropScaleMode.Max, + SaveFormat = FileFormat.Jpeg, + JpegQuality = Quality, + JpegSubsampleMode = ChromaSubsampleMode.Subsample420 + }; + + using var output = new FileStream(this.OutputPath(input, MagicScaler), FileMode.Create); + MagicImageProcessor.ProcessImage(input, output, settings); + } + + public void SkiaCanvasResize(string input) + { + using var original = SKBitmap.Decode(input); + (int width, int height) scaled = this.ScaledSize(original.Width, original.Height, ThumbnailSize); + using var surface = SKSurface.Create(new SKImageInfo(scaled.width, scaled.height, original.ColorType, original.AlphaType)); + using var paint = new SKPaint() { FilterQuality = SKFilterQuality.High }; + SKCanvas canvas = surface.Canvas; + canvas.Scale((float)scaled.width / original.Width); + canvas.DrawBitmap(original, 0, 0, paint); + canvas.Flush(); + + using FileStream output = File.OpenWrite(this.OutputPath(input, SkiaSharpCanvas)); + surface.Snapshot() + .Encode(SKEncodedImageFormat.Jpeg, Quality) + .SaveTo(output); + } + + public void SkiaBitmapResize(string input) + { + using var original = SKBitmap.Decode(input); + (int width, int height) scaled = this.ScaledSize(original.Width, original.Height, ThumbnailSize); + using var resized = original.Resize(new SKImageInfo(scaled.width, scaled.height), SKFilterQuality.High); + if (resized == null) + { + return; + } + + using var image = SKImage.FromBitmap(resized); + using FileStream output = File.OpenWrite(this.OutputPath(input, SkiaSharpBitmap)); + image.Encode(SKEncodedImageFormat.Jpeg, Quality) + .SaveTo(output); + } + + public void NetVipsResize(string input) + { + // Thumbnail to fit a 150x150 square + using var thumb = NetVipsImage.Thumbnail(input, ThumbnailSize, ThumbnailSize); + + // Save the results + thumb.Jpegsave(this.OutputPath(input, NetVips), q: Quality, strip: true); + } + } +} diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStress_NonParallel.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStress_NonParallel.cs new file mode 100644 index 0000000000..99aeaad5e6 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStress_NonParallel.cs @@ -0,0 +1,52 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using BenchmarkDotNet.Attributes; + +namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave +{ + public class LoadResizeSaveStress_NonParallel + { + private LoadResizeSaveStressRunner benchmarks; + + [GlobalSetup] + public void Setup() + { + this.benchmarks = new LoadResizeSaveStressRunner() { ImageCount = 20 }; + this.benchmarks.Init(); + } + + private void ForEachImage(Action action) + { + foreach (string image in this.benchmarks.Images) + { + action(image); + } + } + + [Benchmark(Baseline = true, Description = "System.Drawing Load, Resize, Save")] + public void SystemDrawingBenchmark() => this.ForEachImage(this.benchmarks.SystemDrawingResize); + + [Benchmark(Description = "ImageSharp Load, Resize, Save")] + public void ImageSharpBenchmark() => this.ForEachImage(this.benchmarks.ImageSharpResize); + + [Benchmark(Description = "ImageMagick Load, Resize, Save")] + public void MagickBenchmark() => this.ForEachImage(this.benchmarks.MagickResize); + + [Benchmark(Description = "ImageFree Load, Resize, Save")] + public void FreeImageBenchmark() => this.ForEachImage(this.benchmarks.FreeImageResize); + + [Benchmark(Description = "MagicScaler Load, Resize, Save")] + public void MagicScalerBenchmark() => this.ForEachImage(this.benchmarks.MagicScalerResize); + + [Benchmark(Description = "SkiaSharp Canvas Load, Resize, Save")] + public void SkiaCanvasBenchmark() => this.ForEachImage(this.benchmarks.SkiaCanvasResize); + + [Benchmark(Description = "SkiaSharp Bitmap Load, Resize, Save")] + public void SkiaBitmapBenchmark() => this.ForEachImage(this.benchmarks.SkiaBitmapResize); + + [Benchmark(Description = "NetVips Load, Resize, Save")] + public void NetVipsBenchmark() => this.ForEachImage(this.benchmarks.NetVipsResize); + } +} diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStress_Parallel.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStress_Parallel.cs new file mode 100644 index 0000000000..78f02b71e0 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStress_Parallel.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; + +namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave +{ + [MemoryDiagnoser] + public class LoadResizeSaveStress_Parallel + { + private LoadResizeSaveStressRunner benchmarks; + + [GlobalSetup] + public void Setup() + { + this.benchmarks = new LoadResizeSaveStressRunner() { ImageCount = 20 }; + this.benchmarks.Init(); + } + + private void ForEachImage(Action action) => Parallel.ForEach(this.benchmarks.Images, action); + + [Benchmark(Baseline = true, Description = "System.Drawing Load, Resize, Save - Parallel")] + public void SystemDrawingBenchmarkParallel() => this.ForEachImage(this.benchmarks.SystemDrawingResize); + + [Benchmark(Description = "ImageSharp Load, Resize, Save - Parallel")] + public void ImageSharpBenchmarkParallel() => this.ForEachImage(this.benchmarks.ImageSharpResize); + + [Benchmark(Description = "ImageMagick Load, Resize, Save - Parallel")] + public void MagickBenchmarkParallel() => this.ForEachImage(this.benchmarks.MagickResize); + + [Benchmark(Description = "ImageFree Load, Resize, Save - Parallel")] + public void FreeImageBenchmarkParallel() => this.ForEachImage(this.benchmarks.FreeImageResize); + + [Benchmark(Description = "MagicScaler Load, Resize, Save - Parallel")] + public void MagicScalerBenchmarkParallel() => this.ForEachImage(this.benchmarks.MagicScalerResize); + + [Benchmark(Description = "SkiaSharp Canvas Load, Resize, Save - Parallel")] + public void SkiaCanvasBenchmarkParallel() => this.ForEachImage(this.benchmarks.SkiaCanvasResize); + + [Benchmark(Description = "SkiaSharp Bitmap Load, Resize, Save - Parallel")] + public void SkiaBitmapBenchmarkParallel() => this.ForEachImage(this.benchmarks.SkiaBitmapResize); + + [Benchmark(Description = "NetVips Load, Resize, Save - Parallel")] + public void NetVipsBenchmarkParallel() => this.ForEachImage(this.benchmarks.NetVipsResize); + } +} diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj index a60ac604f1..c4fd2bf701 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj +++ b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj @@ -14,6 +14,7 @@ false Debug;Release;Debug-InnerLoop;Release-InnerLoop false + 9 @@ -31,6 +32,7 @@ + diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs new file mode 100644 index 0000000000..61bdc33b34 --- /dev/null +++ b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs @@ -0,0 +1,100 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Benchmarks.LoadResizeSave; + +namespace SixLabors.ImageSharp.Tests.ProfilingSandbox +{ + internal class LoadResizeSaveParallelMemoryStress + { + private readonly LoadResizeSaveStressRunner benchmarks; + + public LoadResizeSaveParallelMemoryStress() + { + this.benchmarks = new LoadResizeSaveStressRunner(); + this.benchmarks.Init(); + } + + public static void Run() + { + Console.WriteLine(@"Choose a library for image resizing stress test: + +1. System.Drawing +2. ImageSharp +3. MagicScaler +4. SkiaSharp +5. NetVips +6. ImageMagick +7. FreeImage +"); + + ConsoleKey key = Console.ReadKey().Key; + if (key < ConsoleKey.D1 || key > ConsoleKey.D7) + { + Console.WriteLine("Unrecognized command."); + return; + } + + try + { + var lrs = new LoadResizeSaveParallelMemoryStress(); + Console.WriteLine("\nRunning..."); + var timer = new Stopwatch(); + timer.Start(); + + switch (key) + { + case ConsoleKey.D1: + lrs.SystemDrawingBenchmarkParallel(); + break; + case ConsoleKey.D2: + lrs.ImageSharpBenchmarkParallel(); + break; + case ConsoleKey.D3: + lrs.MagicScalerBenchmarkParallel(); + break; + case ConsoleKey.D4: + lrs.SkiaCanvasBenchmarkParallel(); + break; + case ConsoleKey.D5: + lrs.NetVipsBenchmarkParallel(); + break; + case ConsoleKey.D6: + lrs.MagickBenchmarkParallel(); + break; + case ConsoleKey.D7: + lrs.FreeImageBenchmarkParallel(); + break; + } + + timer.Stop(); + Console.WriteLine($"Completed in {timer.ElapsedMilliseconds / 1000.0:f3}sec"); + } + catch (Exception ex) + { + Console.WriteLine(ex.ToString()); + } + } + + private void ForEachImage(Action action) => Parallel.ForEach(this.benchmarks.Images, action); + + private void SystemDrawingBenchmarkParallel() => this.ForEachImage(this.benchmarks.SystemDrawingResize); + + private void ImageSharpBenchmarkParallel() => this.ForEachImage(this.benchmarks.ImageSharpResize); + + private void MagickBenchmarkParallel() => this.ForEachImage(this.benchmarks.MagickResize); + + private void FreeImageBenchmarkParallel() => this.ForEachImage(this.benchmarks.FreeImageResize); + + private void MagicScalerBenchmarkParallel() => this.ForEachImage(this.benchmarks.MagicScalerResize); + + private void SkiaCanvasBenchmarkParallel() => this.ForEachImage(this.benchmarks.SkiaCanvasResize); + + private void SkiaBitmapBenchmarkParallel() => this.ForEachImage(this.benchmarks.SkiaBitmapResize); + + private void NetVipsBenchmarkParallel() => this.ForEachImage(this.benchmarks.NetVipsResize); + } +} diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs index 50a930b6f1..8e03fbbec4 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Diagnostics; using SixLabors.ImageSharp.Tests.Formats.Jpg; using SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations; using SixLabors.ImageSharp.Tests.ProfilingBenchmarks; @@ -31,7 +32,8 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox /// public static void Main(string[] args) { - RunJpegEncoderProfilingTests(); + LoadResizeSaveParallelMemoryStress.Run(); + // RunJpegEncoderProfilingTests(); // RunJpegColorProfilingTests(); // RunDecodeJpegProfilingTests(); // RunToVector4ProfilingTest(); From a94aa889e8b2e16170b70f89804fd8ecba37e552 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 4 Jul 2021 15:01:30 +0200 Subject: [PATCH 02/11] gitignore MemoryStress images --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 475d6e76b0..769a40c6cc 100644 --- a/.gitignore +++ b/.gitignore @@ -221,4 +221,5 @@ artifacts/ # Tests **/Images/ActualOutput **/Images/ReferenceOutput +**/Images/Input/MemoryStress .DS_Store From 2e91fc836301384914b929f137beb999e4bd8cb3 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 4 Jul 2021 15:09:23 +0200 Subject: [PATCH 03/11] add a README --- tests/ImageSharp.Benchmarks/LoadResizeSave/README.md | 7 +++++++ .../LoadResizeSaveParallelMemoryStress.cs | 1 + tests/ImageSharp.Tests.ProfilingSandbox/Program.cs | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 tests/ImageSharp.Benchmarks/LoadResizeSave/README.md diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/README.md b/tests/ImageSharp.Benchmarks/LoadResizeSave/README.md new file mode 100644 index 0000000000..d21f2772b9 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/README.md @@ -0,0 +1,7 @@ +The benchmarks have been adapted from the +[PhotoSauce's MemoryStress project](https://github.com/saucecontrol/core-imaging-playground/tree/beeees/MemoryStress). + +### Setup + +Download the [Bee Heads album](https://www.flickr.com/photos/usgsbiml/albums/72157633925491877) from the USGS Bee Inventory flickr + and extract to folder `\tests\Images\ActualOutput\MemoryStress\`. diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs index 61bdc33b34..54b09b72bb 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs @@ -8,6 +8,7 @@ using SixLabors.ImageSharp.Benchmarks.LoadResizeSave; namespace SixLabors.ImageSharp.Tests.ProfilingSandbox { + // See ImageSharp.Benchmarks/LoadResizeSave/README.md internal class LoadResizeSaveParallelMemoryStress { private readonly LoadResizeSaveStressRunner benchmarks; diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs index 8e03fbbec4..9dd7e4c820 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs @@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox // RunToVector4ProfilingTest(); // RunResizeProfilingTest(); - Console.ReadLine(); + // Console.ReadLine(); } private static void RunJpegEncoderProfilingTests() From 7a91493ddb05332579c989be00df10e69cc3d353 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 4 Jul 2021 18:31:03 +0200 Subject: [PATCH 04/11] unify parallel & non-parallel benchmarks --- .../LoadResizeSaveStressBenchmarks.cs | 72 +++++++++++++++++++ .../LoadResizeSaveStressRunner.cs | 26 +++++++ .../LoadResizeSaveStress_NonParallel.cs | 52 -------------- .../LoadResizeSaveStress_Parallel.cs | 48 ------------- .../LoadResizeSave/README.md | 2 + .../LoadResizeSaveParallelMemoryStress.cs | 59 +++++++++++++-- 6 files changed, 153 insertions(+), 106 deletions(-) create mode 100644 tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs delete mode 100644 tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStress_NonParallel.cs delete mode 100644 tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStress_Parallel.cs diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs new file mode 100644 index 0000000000..add5a72ff9 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs @@ -0,0 +1,72 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; + +namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave +{ + [MemoryDiagnoser] + [ShortRunJob] + public class LoadResizeSaveStressBenchmarks + { + private LoadResizeSaveStressRunner runner; + + [GlobalSetup] + public void Setup() + { + this.runner = new LoadResizeSaveStressRunner() { ImageCount = Environment.ProcessorCount }; + Console.WriteLine("ImageCount:" + this.runner.ImageCount); + this.runner.Init(); + } + + private void ForEachImage(Action action, int maxDegreeOfParallelism) + { + this.runner.MaxDegreeOfParallelism = maxDegreeOfParallelism; + this.runner.ForEachImageParallel(action); + } + + public int[] ParallelismValues { get; } = + { + Environment.ProcessorCount, + Environment.ProcessorCount / 2, + Environment.ProcessorCount / 4, + 1 + }; + + [Benchmark(Baseline = true)] + [ArgumentsSource(nameof(ParallelismValues))] + public void SystemDrawing(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.SystemDrawingResize, maxDegreeOfParallelism); + + [Benchmark] + [ArgumentsSource(nameof(ParallelismValues))] + public void ImageSharp(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.ImageSharpResize, maxDegreeOfParallelism); + + [Benchmark] + [ArgumentsSource(nameof(ParallelismValues))] + public void Magick(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.MagickResize, maxDegreeOfParallelism); + + [Benchmark] + [ArgumentsSource(nameof(ParallelismValues))] + public void FreeImage(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.FreeImageResize, maxDegreeOfParallelism); + + [Benchmark] + [ArgumentsSource(nameof(ParallelismValues))] + public void MagicScaler(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.MagicScalerResize, maxDegreeOfParallelism); + + [Benchmark] + [ArgumentsSource(nameof(ParallelismValues))] + public void SkiaCanvas(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.SkiaCanvasResize, maxDegreeOfParallelism); + + [Benchmark] + [ArgumentsSource(nameof(ParallelismValues))] + public void SkiaBitmap(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.SkiaBitmapResize, maxDegreeOfParallelism); + + [Benchmark] + [ArgumentsSource(nameof(ParallelismValues))] + public void NetVips(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.NetVipsResize, maxDegreeOfParallelism); + } +} diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs index 77585213fd..bb9b68d65a 100644 --- a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs @@ -8,6 +8,7 @@ using System.Drawing.Imaging; using System.IO; using System.Linq; using System.Runtime.InteropServices; +using System.Threading.Tasks; using FreeImageAPI; using ImageMagick; using PhotoSauce.MagicScaler; @@ -42,10 +43,14 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave public string[] Images { get; private set; } + public double TotalProcessedMegapixels { get; private set; } + private string outputDirectory; public int ImageCount { get; set; } = int.MaxValue; + public int MaxDegreeOfParallelism { get; set; } = -1; + public void Init() { if (RuntimeInformation.OSArchitecture is Architecture.X86 or Architecture.X64) @@ -67,6 +72,17 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave this.outputDirectory = TestEnvironment.CreateOutputDirectory("MemoryStress"); } + public void ForEachImageParallel(Action action) => Parallel.ForEach( + this.Images, + new ParallelOptions { MaxDegreeOfParallelism = this.MaxDegreeOfParallelism }, + action); + + private void IncreaseTotalMegapixels(int width, int height) + { + double pixels = width * (double)height; + this.TotalProcessedMegapixels += pixels / 1_000_000.0; + } + private string OutputPath(string inputPath, string postfix) => Path.Combine( this.outputDirectory, @@ -92,6 +108,8 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave public void SystemDrawingResize(string input) { using var image = SystemDrawingImage.FromFile(input, true); + this.IncreaseTotalMegapixels(image.Width, image.Height); + (int width, int height) scaled = this.ScaledSize(image.Width, image.Height, ThumbnailSize); var resized = new Bitmap(scaled.width, scaled.height); using var graphics = Graphics.FromImage(resized); @@ -116,6 +134,8 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave // Resize it to fit a 150x150 square using var image = ImageSharpImage.Load(input); + this.IncreaseTotalMegapixels(image.Width, image.Height); + image.Mutate(i => i.Resize(new ResizeOptions { Size = new ImageSharpSize(ThumbnailSize, ThumbnailSize), @@ -132,6 +152,7 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave public void MagickResize(string input) { using var image = new MagickImage(input); + this.IncreaseTotalMegapixels(image.Width, image.Height); // Resize it to fit a 150x150 square image.Resize(ThumbnailSize, ThumbnailSize); @@ -149,6 +170,8 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave public void FreeImageResize(string input) { using var original = FreeImageBitmap.FromFile(input); + this.IncreaseTotalMegapixels(original.Width, original.Height); + (int width, int height) scaled = this.ScaledSize(original.Width, original.Height, ThumbnailSize); var resized = new FreeImageBitmap(original, scaled.width, scaled.height); @@ -172,6 +195,7 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave JpegSubsampleMode = ChromaSubsampleMode.Subsample420 }; + // TODO: Is there a way to capture input dimensions for IncreaseTotalMegapixels? using var output = new FileStream(this.OutputPath(input, MagicScaler), FileMode.Create); MagicImageProcessor.ProcessImage(input, output, settings); } @@ -179,6 +203,7 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave public void SkiaCanvasResize(string input) { using var original = SKBitmap.Decode(input); + this.IncreaseTotalMegapixels(original.Width, original.Height); (int width, int height) scaled = this.ScaledSize(original.Width, original.Height, ThumbnailSize); using var surface = SKSurface.Create(new SKImageInfo(scaled.width, scaled.height, original.ColorType, original.AlphaType)); using var paint = new SKPaint() { FilterQuality = SKFilterQuality.High }; @@ -196,6 +221,7 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave public void SkiaBitmapResize(string input) { using var original = SKBitmap.Decode(input); + this.IncreaseTotalMegapixels(original.Width, original.Height); (int width, int height) scaled = this.ScaledSize(original.Width, original.Height, ThumbnailSize); using var resized = original.Resize(new SKImageInfo(scaled.width, scaled.height), SKFilterQuality.High); if (resized == null) diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStress_NonParallel.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStress_NonParallel.cs deleted file mode 100644 index 99aeaad5e6..0000000000 --- a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStress_NonParallel.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using BenchmarkDotNet.Attributes; - -namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave -{ - public class LoadResizeSaveStress_NonParallel - { - private LoadResizeSaveStressRunner benchmarks; - - [GlobalSetup] - public void Setup() - { - this.benchmarks = new LoadResizeSaveStressRunner() { ImageCount = 20 }; - this.benchmarks.Init(); - } - - private void ForEachImage(Action action) - { - foreach (string image in this.benchmarks.Images) - { - action(image); - } - } - - [Benchmark(Baseline = true, Description = "System.Drawing Load, Resize, Save")] - public void SystemDrawingBenchmark() => this.ForEachImage(this.benchmarks.SystemDrawingResize); - - [Benchmark(Description = "ImageSharp Load, Resize, Save")] - public void ImageSharpBenchmark() => this.ForEachImage(this.benchmarks.ImageSharpResize); - - [Benchmark(Description = "ImageMagick Load, Resize, Save")] - public void MagickBenchmark() => this.ForEachImage(this.benchmarks.MagickResize); - - [Benchmark(Description = "ImageFree Load, Resize, Save")] - public void FreeImageBenchmark() => this.ForEachImage(this.benchmarks.FreeImageResize); - - [Benchmark(Description = "MagicScaler Load, Resize, Save")] - public void MagicScalerBenchmark() => this.ForEachImage(this.benchmarks.MagicScalerResize); - - [Benchmark(Description = "SkiaSharp Canvas Load, Resize, Save")] - public void SkiaCanvasBenchmark() => this.ForEachImage(this.benchmarks.SkiaCanvasResize); - - [Benchmark(Description = "SkiaSharp Bitmap Load, Resize, Save")] - public void SkiaBitmapBenchmark() => this.ForEachImage(this.benchmarks.SkiaBitmapResize); - - [Benchmark(Description = "NetVips Load, Resize, Save")] - public void NetVipsBenchmark() => this.ForEachImage(this.benchmarks.NetVipsResize); - } -} diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStress_Parallel.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStress_Parallel.cs deleted file mode 100644 index 78f02b71e0..0000000000 --- a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStress_Parallel.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Threading.Tasks; -using BenchmarkDotNet.Attributes; - -namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave -{ - [MemoryDiagnoser] - public class LoadResizeSaveStress_Parallel - { - private LoadResizeSaveStressRunner benchmarks; - - [GlobalSetup] - public void Setup() - { - this.benchmarks = new LoadResizeSaveStressRunner() { ImageCount = 20 }; - this.benchmarks.Init(); - } - - private void ForEachImage(Action action) => Parallel.ForEach(this.benchmarks.Images, action); - - [Benchmark(Baseline = true, Description = "System.Drawing Load, Resize, Save - Parallel")] - public void SystemDrawingBenchmarkParallel() => this.ForEachImage(this.benchmarks.SystemDrawingResize); - - [Benchmark(Description = "ImageSharp Load, Resize, Save - Parallel")] - public void ImageSharpBenchmarkParallel() => this.ForEachImage(this.benchmarks.ImageSharpResize); - - [Benchmark(Description = "ImageMagick Load, Resize, Save - Parallel")] - public void MagickBenchmarkParallel() => this.ForEachImage(this.benchmarks.MagickResize); - - [Benchmark(Description = "ImageFree Load, Resize, Save - Parallel")] - public void FreeImageBenchmarkParallel() => this.ForEachImage(this.benchmarks.FreeImageResize); - - [Benchmark(Description = "MagicScaler Load, Resize, Save - Parallel")] - public void MagicScalerBenchmarkParallel() => this.ForEachImage(this.benchmarks.MagicScalerResize); - - [Benchmark(Description = "SkiaSharp Canvas Load, Resize, Save - Parallel")] - public void SkiaCanvasBenchmarkParallel() => this.ForEachImage(this.benchmarks.SkiaCanvasResize); - - [Benchmark(Description = "SkiaSharp Bitmap Load, Resize, Save - Parallel")] - public void SkiaBitmapBenchmarkParallel() => this.ForEachImage(this.benchmarks.SkiaBitmapResize); - - [Benchmark(Description = "NetVips Load, Resize, Save - Parallel")] - public void NetVipsBenchmarkParallel() => this.ForEachImage(this.benchmarks.NetVipsResize); - } -} diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/README.md b/tests/ImageSharp.Benchmarks/LoadResizeSave/README.md index d21f2772b9..6cb48eb48c 100644 --- a/tests/ImageSharp.Benchmarks/LoadResizeSave/README.md +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/README.md @@ -5,3 +5,5 @@ Download the [Bee Heads album](https://www.flickr.com/photos/usgsbiml/albums/72157633925491877) from the USGS Bee Inventory flickr and extract to folder `\tests\Images\ActualOutput\MemoryStress\`. + + diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs index 54b09b72bb..fdf6860806 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics; +using System.Text; using System.Threading.Tasks; using SixLabors.ImageSharp.Benchmarks.LoadResizeSave; @@ -13,12 +14,14 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox { private readonly LoadResizeSaveStressRunner benchmarks; - public LoadResizeSaveParallelMemoryStress() + private LoadResizeSaveParallelMemoryStress() { this.benchmarks = new LoadResizeSaveStressRunner(); this.benchmarks.Init(); } + private double TotalProcessedMegapixels => this.benchmarks.TotalProcessedMegapixels; + public static void Run() { Console.WriteLine(@"Choose a library for image resizing stress test: @@ -42,9 +45,11 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox try { var lrs = new LoadResizeSaveParallelMemoryStress(); - Console.WriteLine("\nRunning..."); - var timer = new Stopwatch(); - timer.Start(); + lrs.benchmarks.MaxDegreeOfParallelism = 10; + + Console.WriteLine($"\nEnvironment.ProcessorCount={Environment.ProcessorCount}"); + Console.WriteLine($"Running with MaxDegreeOfParallelism={lrs.benchmarks.MaxDegreeOfParallelism} ..."); + var timer = Stopwatch.StartNew(); switch (key) { @@ -72,7 +77,9 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox } timer.Stop(); - Console.WriteLine($"Completed in {timer.ElapsedMilliseconds / 1000.0:f3}sec"); + var stats = Stats.Create(timer, lrs.TotalProcessedMegapixels); + Console.WriteLine("Done. TotalProcessedMegapixels: " + lrs.TotalProcessedMegapixels); + Console.WriteLine(stats.GetMarkdown()); } catch (Exception ex) { @@ -80,7 +87,39 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox } } - private void ForEachImage(Action action) => Parallel.ForEach(this.benchmarks.Images, action); + record Stats(double TotalSeconds, double TotalMegapixels, double MegapixelsPerSec, double MegapixelsPerSecPerCpu) + { + public static Stats Create(Stopwatch sw, double totalMegapixels) + { + double totalSeconds = sw.ElapsedMilliseconds / 1000.0; + double megapixelsPerSec = totalMegapixels / totalSeconds; + double megapixelsPerSecPerCpu = megapixelsPerSec / Environment.ProcessorCount; + return new Stats(totalSeconds, totalMegapixels, megapixelsPerSec, megapixelsPerSecPerCpu); + } + + public string GetMarkdown() + { + var bld = new StringBuilder(); + bld.AppendLine($"| {nameof(TotalSeconds)} | {nameof(MegapixelsPerSec)} | {nameof(MegapixelsPerSecPerCpu)} |"); + bld.AppendLine( + $"| {L(nameof(TotalSeconds))} | {L(nameof(MegapixelsPerSec))} | {L(nameof(MegapixelsPerSecPerCpu))} |"); + + bld.Append("| "); + bld.AppendFormat(F(nameof(this.TotalSeconds)), this.TotalSeconds); + bld.Append(" | "); + bld.AppendFormat(F(nameof(this.MegapixelsPerSec)), this.MegapixelsPerSec); + bld.Append(" | "); + bld.AppendFormat(F(nameof(this.MegapixelsPerSecPerCpu)), this.MegapixelsPerSecPerCpu); + bld.AppendLine(" |"); + + return bld.ToString(); + + static string L(string header) => new ('-', header.Length); + static string F(string column) => $"{{0,{column.Length}:f3}}"; + } + } + + private void ForEachImage(Action action) => this.benchmarks.ForEachImageParallel(action); private void SystemDrawingBenchmarkParallel() => this.ForEachImage(this.benchmarks.SystemDrawingResize); @@ -99,3 +138,11 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox private void NetVipsBenchmarkParallel() => this.ForEachImage(this.benchmarks.NetVipsResize); } } + +// https://stackoverflow.com/questions/64749385/predefined-type-system-runtime-compilerservices-isexternalinit-is-not-defined +namespace System.Runtime.CompilerServices +{ + internal static class IsExternalInit + { + } +} From 5a9826e85840f882fcd4bd20607b1bfd8ac7e6e0 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 4 Jul 2021 19:44:15 +0200 Subject: [PATCH 05/11] remove FreeImage --- tests/Directory.Build.targets | 1 - .../ImageSharp.Benchmarks.csproj | 4 ++-- .../LoadResizeSaveStressBenchmarks.cs | 11 ----------- .../LoadResizeSaveStressRunner.cs | 18 ------------------ .../LoadResizeSaveParallelMemoryStress.cs | 10 +--------- 5 files changed, 3 insertions(+), 41 deletions(-) diff --git a/tests/Directory.Build.targets b/tests/Directory.Build.targets index 5ca7d2b937..238046d759 100644 --- a/tests/Directory.Build.targets +++ b/tests/Directory.Build.targets @@ -30,7 +30,6 @@ - diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj index 30fbbbda98..248b14a9d6 100644 --- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj +++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj @@ -41,10 +41,10 @@ - - + diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs index add5a72ff9..dc2f49b1eb 100644 --- a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs @@ -2,9 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using BenchmarkDotNet.Attributes; namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave @@ -49,18 +46,10 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave [ArgumentsSource(nameof(ParallelismValues))] public void Magick(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.MagickResize, maxDegreeOfParallelism); - [Benchmark] - [ArgumentsSource(nameof(ParallelismValues))] - public void FreeImage(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.FreeImageResize, maxDegreeOfParallelism); - [Benchmark] [ArgumentsSource(nameof(ParallelismValues))] public void MagicScaler(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.MagicScalerResize, maxDegreeOfParallelism); - [Benchmark] - [ArgumentsSource(nameof(ParallelismValues))] - public void SkiaCanvas(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.SkiaCanvasResize, maxDegreeOfParallelism); - [Benchmark] [ArgumentsSource(nameof(ParallelismValues))] public void SkiaBitmap(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.SkiaBitmapResize, maxDegreeOfParallelism); diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs index bb9b68d65a..bd3a8e62d7 100644 --- a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs @@ -9,7 +9,6 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Threading.Tasks; -using FreeImageAPI; using ImageMagick; using PhotoSauce.MagicScaler; using SixLabors.ImageSharp.Formats.Jpeg; @@ -31,7 +30,6 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave private const string SystemDrawing = nameof(SystemDrawing); private const string MagickNET = nameof(MagickNET); private const string NetVips = nameof(NetVips); - private const string FreeImage = nameof(FreeImage); private const string MagicScaler = nameof(MagicScaler); private const string SkiaSharpCanvas = nameof(SkiaSharpCanvas); private const string SkiaSharpBitmap = nameof(SkiaSharpBitmap); @@ -167,22 +165,6 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave image.Write(this.OutputPath(input, MagickNET)); } - public void FreeImageResize(string input) - { - using var original = FreeImageBitmap.FromFile(input); - this.IncreaseTotalMegapixels(original.Width, original.Height); - - (int width, int height) scaled = this.ScaledSize(original.Width, original.Height, ThumbnailSize); - var resized = new FreeImageBitmap(original, scaled.width, scaled.height); - - // JPEG_QUALITYGOOD is 75 JPEG. - // JPEG_BASELINE strips metadata (EXIF, etc.) - resized.Save( - this.OutputPath(input, FreeImage), - FREE_IMAGE_FORMAT.FIF_JPEG, - FREE_IMAGE_SAVE_FLAGS.JPEG_QUALITYGOOD | FREE_IMAGE_SAVE_FLAGS.JPEG_BASELINE); - } - public void MagicScalerResize(string input) { var settings = new ProcessImageSettings() diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs index fdf6860806..fac04e94d9 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs @@ -32,7 +32,6 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox 4. SkiaSharp 5. NetVips 6. ImageMagick -7. FreeImage "); ConsoleKey key = Console.ReadKey().Key; @@ -63,7 +62,7 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox lrs.MagicScalerBenchmarkParallel(); break; case ConsoleKey.D4: - lrs.SkiaCanvasBenchmarkParallel(); + lrs.SkiaBitmapBenchmarkParallel(); break; case ConsoleKey.D5: lrs.NetVipsBenchmarkParallel(); @@ -71,9 +70,6 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox case ConsoleKey.D6: lrs.MagickBenchmarkParallel(); break; - case ConsoleKey.D7: - lrs.FreeImageBenchmarkParallel(); - break; } timer.Stop(); @@ -127,12 +123,8 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox private void MagickBenchmarkParallel() => this.ForEachImage(this.benchmarks.MagickResize); - private void FreeImageBenchmarkParallel() => this.ForEachImage(this.benchmarks.FreeImageResize); - private void MagicScalerBenchmarkParallel() => this.ForEachImage(this.benchmarks.MagicScalerResize); - private void SkiaCanvasBenchmarkParallel() => this.ForEachImage(this.benchmarks.SkiaCanvasResize); - private void SkiaBitmapBenchmarkParallel() => this.ForEachImage(this.benchmarks.SkiaBitmapResize); private void NetVipsBenchmarkParallel() => this.ForEachImage(this.benchmarks.NetVipsResize); From 19f3559b9261ee63bcd0b0ad319ede5a403faef1 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 4 Jul 2021 23:11:16 +0200 Subject: [PATCH 06/11] minor fix --- .../LoadResizeSave/LoadResizeSaveStressBenchmarks.cs | 1 + .../LoadResizeSaveParallelMemoryStress.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs index dc2f49b1eb..6dad6944ea 100644 --- a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs @@ -6,6 +6,7 @@ using BenchmarkDotNet.Attributes; namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave { + // See README.md for instructions about initialization. [MemoryDiagnoser] [ShortRunJob] public class LoadResizeSaveStressBenchmarks diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs index fac04e94d9..ecf76fb480 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs @@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox "); ConsoleKey key = Console.ReadKey().Key; - if (key < ConsoleKey.D1 || key > ConsoleKey.D7) + if (key < ConsoleKey.D1 || key > ConsoleKey.D6) { Console.WriteLine("Unrecognized command."); return; From 21544fdbbee6682fa837fbf96df7abc4896da41a Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 4 Jul 2021 23:32:23 +0200 Subject: [PATCH 07/11] fix StyleCop warnings --- .../LoadResizeSaveParallelMemoryStress.cs | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs index ecf76fb480..a4b27e0a51 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs @@ -4,7 +4,7 @@ using System; using System.Diagnostics; using System.Text; -using System.Threading.Tasks; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; using SixLabors.ImageSharp.Benchmarks.LoadResizeSave; namespace SixLabors.ImageSharp.Tests.ProfilingSandbox @@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox } timer.Stop(); - var stats = Stats.Create(timer, lrs.TotalProcessedMegapixels); + var stats = new Stats(timer, lrs.TotalProcessedMegapixels); Console.WriteLine("Done. TotalProcessedMegapixels: " + lrs.TotalProcessedMegapixels); Console.WriteLine(stats.GetMarkdown()); } @@ -83,22 +83,30 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox } } - record Stats(double TotalSeconds, double TotalMegapixels, double MegapixelsPerSec, double MegapixelsPerSecPerCpu) + private struct Stats { - public static Stats Create(Stopwatch sw, double totalMegapixels) + public double TotalSeconds { get; } + + public double TotalMegapixels { get; } + + public double MegapixelsPerSec { get; } + + public double MegapixelsPerSecPerCpu { get; } + + public Stats(Stopwatch sw, double totalMegapixels) { - double totalSeconds = sw.ElapsedMilliseconds / 1000.0; - double megapixelsPerSec = totalMegapixels / totalSeconds; - double megapixelsPerSecPerCpu = megapixelsPerSec / Environment.ProcessorCount; - return new Stats(totalSeconds, totalMegapixels, megapixelsPerSec, megapixelsPerSecPerCpu); + this.TotalMegapixels = totalMegapixels; + this.TotalSeconds = sw.ElapsedMilliseconds / 1000.0; + this.MegapixelsPerSec = totalMegapixels / this.TotalSeconds; + this.MegapixelsPerSecPerCpu = this.MegapixelsPerSec / Environment.ProcessorCount; } public string GetMarkdown() { var bld = new StringBuilder(); - bld.AppendLine($"| {nameof(TotalSeconds)} | {nameof(MegapixelsPerSec)} | {nameof(MegapixelsPerSecPerCpu)} |"); + bld.AppendLine($"| {nameof(this.TotalSeconds)} | {nameof(this.MegapixelsPerSec)} | {nameof(this.MegapixelsPerSecPerCpu)} |"); bld.AppendLine( - $"| {L(nameof(TotalSeconds))} | {L(nameof(MegapixelsPerSec))} | {L(nameof(MegapixelsPerSecPerCpu))} |"); + $"| {L(nameof(this.TotalSeconds))} | {L(nameof(this.MegapixelsPerSec))} | {L(nameof(this.MegapixelsPerSecPerCpu))} |"); bld.Append("| "); bld.AppendFormat(F(nameof(this.TotalSeconds)), this.TotalSeconds); @@ -130,11 +138,3 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox private void NetVipsBenchmarkParallel() => this.ForEachImage(this.benchmarks.NetVipsResize); } } - -// https://stackoverflow.com/questions/64749385/predefined-type-system-runtime-compilerservices-isexternalinit-is-not-defined -namespace System.Runtime.CompilerServices -{ - internal static class IsExternalInit - { - } -} From 2566879ff5be41a573c6c22a04432576512dbad8 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 4 Jul 2021 23:45:19 +0200 Subject: [PATCH 08/11] workaround NuGet restore issues --- .../ImageSharp.Benchmarks.csproj | 11 ++++++----- .../LoadResizeSaveParallelMemoryStress.cs | 1 - 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj index 248b14a9d6..5888b3c04a 100644 --- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj +++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj @@ -41,12 +41,13 @@ - - - + + + + - + + diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs index a4b27e0a51..5a1a242e24 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs @@ -4,7 +4,6 @@ using System; using System.Diagnostics; using System.Text; -using Microsoft.VisualStudio.TestPlatform.ObjectModel; using SixLabors.ImageSharp.Benchmarks.LoadResizeSave; namespace SixLabors.ImageSharp.Tests.ProfilingSandbox From 17b0e3ed82c99d4fa921788fa360ff4eb3c5c53b Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 5 Jul 2021 13:11:13 +1000 Subject: [PATCH 09/11] Fix reference issues --- tests/Directory.Build.targets | 22 +++++++++---------- .../ImageSharp.Benchmarks.csproj | 12 +++++----- .../ImageSharp.Tests.ProfilingSandbox.csproj | 5 +++++ 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/tests/Directory.Build.targets b/tests/Directory.Build.targets index 238046d759..53b4f9632c 100644 --- a/tests/Directory.Build.targets +++ b/tests/Directory.Build.targets @@ -18,23 +18,21 @@ - - + + - + + + + - - - - - - - - - + + + + diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj index 5888b3c04a..84b83ee14a 100644 --- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj +++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj @@ -38,16 +38,14 @@ + + + + + - - - - - - - diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj index c4fd2bf701..10deb24c63 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj +++ b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj @@ -38,6 +38,11 @@ + + + + + From 69687da552679c6af89ca49b849ac587107aeecd Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 5 Jul 2021 13:21:14 +1000 Subject: [PATCH 10/11] Add missing ref for macOS --- tests/ImageSharp.Tests/ImageSharp.Tests.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index b8d44d0d1e..30bd544fa3 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -39,6 +39,7 @@ + From 20040bd89a255fa9b0b6eb51d773be93640748b9 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 5 Jul 2021 15:11:13 +0200 Subject: [PATCH 11/11] add ability to filter for Baseline/Progressive --- .../LoadResizeSaveStressBenchmarks.cs | 11 +++- .../LoadResizeSaveStressRunner.cs | 53 ++++++++++++++++++- .../LoadResizeSaveParallelMemoryStress.cs | 7 ++- 3 files changed, 66 insertions(+), 5 deletions(-) diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs index 6dad6944ea..f1f7de3dc6 100644 --- a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs @@ -13,11 +13,18 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave { private LoadResizeSaveStressRunner runner; + // private const JpegKind Filter = JpegKind.Progressive; + private const JpegKind Filter = JpegKind.Any; + [GlobalSetup] public void Setup() { - this.runner = new LoadResizeSaveStressRunner() { ImageCount = Environment.ProcessorCount }; - Console.WriteLine("ImageCount:" + this.runner.ImageCount); + this.runner = new LoadResizeSaveStressRunner() + { + ImageCount = Environment.ProcessorCount, + Filter = Filter + }; + Console.WriteLine($"ImageCount: {this.runner.ImageCount} Filter: {Filter}"); this.runner.Init(); } diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs index bd3a8e62d7..c15f641b4a 100644 --- a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs @@ -22,6 +22,13 @@ using SystemDrawingImage = System.Drawing.Image; namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave { + public enum JpegKind + { + Baseline = 1, + Progressive = 2, + Any = Baseline | Progressive + } + public class LoadResizeSaveStressRunner { private const int ThumbnailSize = 150; @@ -49,6 +56,43 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave public int MaxDegreeOfParallelism { get; set; } = -1; + public JpegKind Filter { get; set; } + + private static readonly string[] ProgressiveFiles = + { + "ancyloscelis-apiformis-m-paraguay-face_2014-08-08-095255-zs-pmax_15046500892_o.jpg", + "acanthopus-excellens-f-face-brasil_2014-08-06-132105-zs-pmax_14792513890_o.jpg", + "bee-ceratina-monster-f-ukraine-face_2014-08-09-123342-zs-pmax_15068816101_o.jpg", + "bombus-eximias-f-tawain-face_2014-08-10-094449-zs-pmax_15155452565_o.jpg", + "ceratina-14507h1-m-vietnam-face_2014-08-09-163218-zs-pmax_15096718245_o.jpg", + "ceratina-buscki-f-panama-face_2014-11-25-140413-zs-pmax_15923736081_o.jpg", + "ceratina-tricolor-f-panama-face2_2014-08-29-160402-zs-pmax_14906318297_o.jpg", + "ceratina-tricolor-f-panama-face_2014-08-29-160001-zs-pmax_14906300608_o.jpg", + "ceratina-tricolor-m-panama-face_2014-08-29-162821-zs-pmax_15069878876_o.jpg", + "coelioxys-cayennensis-f-argentina-face_2014-08-09-171932-zs-pmax_14914109737_o.jpg", + "ctenocolletes-smaragdinus-f-australia-face_2014-08-08-134825-zs-pmax_14865269708_o.jpg", + "diphaglossa-gayi-f-face-chile_2014-08-04-180547-zs-pmax_14918891472_o.jpg", + "hylaeus-nubilosus-f-australia-face_2014-08-14-121100-zs-pmax_15049602149_o.jpg", + "hypanthidioides-arenaria-f-face-brazil_2014-08-06-061201-zs-pmax_14770371360_o.jpg", + "megachile-chalicodoma-species-f-morocco-face_2014-08-14-124840-zs-pmax_15217084686_o.jpg", + "megachile-species-f-15266b06-face-kenya_2014-08-06-161044-zs-pmax_14994381392_o.jpg", + "megalopta-genalis-m-face-panama-barocolorado_2014-09-19-164939-zs-pmax_15121397069_o.jpg", + "melitta-haemorrhoidalis-m--england-face_2014-11-02-014026-zs-pmax-recovered_15782113675_o.jpg", + "nomia-heart-antennae-m-15266b02-face-kenya_2014-08-04-195216-zs-pmax_14922843736_o.jpg", + "nomia-species-m-oman-face_2014-08-09-192602-zs-pmax_15128732411_o.jpg", + "nomia-spiney-m-vietnam-face_2014-08-09-213126-zs-pmax_15191389705_o.jpg", + "ochreriades-fasciata-m-face-israel_2014-08-06-084407-zs-pmax_14965515571_o.jpg", + "osmia-brevicornisf-jaw-kyrgystan_2014-08-08-103333-zs-pmax_14865267787_o.jpg", + "pachyanthidium-aff-benguelense-f-6711f07-face_2014-08-07-112830-zs-pmax_15018069042_o.jpg", + "pachymelus-bicolor-m-face-madagascar_2014-08-06-134930-zs-pmax_14801667477_o.jpg", + "psaenythia-species-m-argentina-face_2014-08-07-163754-zs-pmax_15007018976_o.jpg", + "stingless-bee-1-f-face-peru_2014-07-30-123322-zs-pmax_15633797167_o.jpg", + "triepeolus-simplex-m-face-md-kent-county_2014-07-22-100937-zs-pmax_14805405233_o.jpg", + "washed-megachile-f-face-chile_2014-08-06-103414-zs-pmax_14977843152_o.jpg", + "xylocopa-balck-violetwing-f-kyrgystan-angle_2014-08-09-182433-zs-pmax_15123416061_o.jpg", + "xylocopa-india-yellow-m-india-face_2014-08-10-111701-zs-pmax_15166559172_o.jpg", + }; + public void Init() { if (RuntimeInformation.OSArchitecture is Architecture.X86 or Architecture.X64) @@ -64,10 +108,17 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave } // Get at most this.ImageCount images from there - this.Images = Directory.EnumerateFiles(imageDirectory).Take(this.ImageCount).ToArray(); + bool FilterFunc(string f) => this.Filter.HasFlag(GetJpegType(f)); + + this.Images = Directory.EnumerateFiles(imageDirectory).Where(FilterFunc).Take(this.ImageCount).ToArray(); // Create the output directory next to the images directory this.outputDirectory = TestEnvironment.CreateOutputDirectory("MemoryStress"); + + static JpegKind GetJpegType(string f) => + ProgressiveFiles.Any(p => f.EndsWith(p, StringComparison.OrdinalIgnoreCase)) + ? JpegKind.Progressive + : JpegKind.Baseline; } public void ForEachImageParallel(Action action) => Parallel.ForEach( diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs index 5a1a242e24..2aadf02eb9 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs @@ -15,7 +15,11 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox private LoadResizeSaveParallelMemoryStress() { - this.benchmarks = new LoadResizeSaveStressRunner(); + this.benchmarks = new LoadResizeSaveStressRunner() + { + // MaxDegreeOfParallelism = 10, + // Filter = JpegKind.Baseline + }; this.benchmarks.Init(); } @@ -43,7 +47,6 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox try { var lrs = new LoadResizeSaveParallelMemoryStress(); - lrs.benchmarks.MaxDegreeOfParallelism = 10; Console.WriteLine($"\nEnvironment.ProcessorCount={Environment.ProcessorCount}"); Console.WriteLine($"Running with MaxDegreeOfParallelism={lrs.benchmarks.MaxDegreeOfParallelism} ...");