From f37b46e56760fda5b761bf0e7cdd6404d0e66e12 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 4 Jul 2021 13:23:39 +0200 Subject: [PATCH] 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();