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
diff --git a/tests/Directory.Build.targets b/tests/Directory.Build.targets
index 9c17881452..53b4f9632c 100644
--- a/tests/Directory.Build.targets
+++ b/tests/Directory.Build.targets
@@ -18,17 +18,21 @@
-
-
+
+
-
+
+
+
+
-
-
-
+
+
+
+
diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj
index 17f6068d40..84b83ee14a 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
@@ -38,8 +38,13 @@
+
+
+
+
+
diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs
new file mode 100644
index 0000000000..f1f7de3dc6
--- /dev/null
+++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs
@@ -0,0 +1,69 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using BenchmarkDotNet.Attributes;
+
+namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave
+{
+ // See README.md for instructions about initialization.
+ [MemoryDiagnoser]
+ [ShortRunJob]
+ public class LoadResizeSaveStressBenchmarks
+ {
+ 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,
+ Filter = Filter
+ };
+ Console.WriteLine($"ImageCount: {this.runner.ImageCount} Filter: {Filter}");
+ 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 MagicScaler(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.MagicScalerResize, 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
new file mode 100644
index 0000000000..c15f641b4a
--- /dev/null
+++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs
@@ -0,0 +1,280 @@
+// 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 System.Threading.Tasks;
+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 enum JpegKind
+ {
+ Baseline = 1,
+ Progressive = 2,
+ Any = Baseline | Progressive
+ }
+
+ 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 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; }
+
+ public double TotalProcessedMegapixels { get; private set; }
+
+ private string outputDirectory;
+
+ public int ImageCount { get; set; } = int.MaxValue;
+
+ 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)
+ {
+ // 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
+ 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(
+ 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,
+ 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);
+ 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);
+ 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);
+ this.IncreaseTotalMegapixels(image.Width, image.Height);
+
+ 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);
+ this.IncreaseTotalMegapixels(image.Width, image.Height);
+
+ // 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 MagicScalerResize(string input)
+ {
+ var settings = new ProcessImageSettings()
+ {
+ Width = ThumbnailSize,
+ Height = ThumbnailSize,
+ ResizeMode = CropScaleMode.Max,
+ SaveFormat = FileFormat.Jpeg,
+ JpegQuality = Quality,
+ 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);
+ }
+
+ 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 };
+ 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);
+ 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)
+ {
+ 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/README.md b/tests/ImageSharp.Benchmarks/LoadResizeSave/README.md
new file mode 100644
index 0000000000..6cb48eb48c
--- /dev/null
+++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/README.md
@@ -0,0 +1,9 @@
+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/ImageSharp.Tests.ProfilingSandbox.csproj b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj
index a60ac604f1..10deb24c63 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,11 +32,17 @@
+
+
+
+
+
+
diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs
new file mode 100644
index 0000000000..2aadf02eb9
--- /dev/null
+++ b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs
@@ -0,0 +1,142 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Diagnostics;
+using System.Text;
+using SixLabors.ImageSharp.Benchmarks.LoadResizeSave;
+
+namespace SixLabors.ImageSharp.Tests.ProfilingSandbox
+{
+ // See ImageSharp.Benchmarks/LoadResizeSave/README.md
+ internal class LoadResizeSaveParallelMemoryStress
+ {
+ private readonly LoadResizeSaveStressRunner benchmarks;
+
+ private LoadResizeSaveParallelMemoryStress()
+ {
+ this.benchmarks = new LoadResizeSaveStressRunner()
+ {
+ // MaxDegreeOfParallelism = 10,
+ // Filter = JpegKind.Baseline
+ };
+ this.benchmarks.Init();
+ }
+
+ private double TotalProcessedMegapixels => this.benchmarks.TotalProcessedMegapixels;
+
+ 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
+");
+
+ ConsoleKey key = Console.ReadKey().Key;
+ if (key < ConsoleKey.D1 || key > ConsoleKey.D6)
+ {
+ Console.WriteLine("Unrecognized command.");
+ return;
+ }
+
+ try
+ {
+ var lrs = new LoadResizeSaveParallelMemoryStress();
+
+ Console.WriteLine($"\nEnvironment.ProcessorCount={Environment.ProcessorCount}");
+ Console.WriteLine($"Running with MaxDegreeOfParallelism={lrs.benchmarks.MaxDegreeOfParallelism} ...");
+ var timer = Stopwatch.StartNew();
+
+ switch (key)
+ {
+ case ConsoleKey.D1:
+ lrs.SystemDrawingBenchmarkParallel();
+ break;
+ case ConsoleKey.D2:
+ lrs.ImageSharpBenchmarkParallel();
+ break;
+ case ConsoleKey.D3:
+ lrs.MagicScalerBenchmarkParallel();
+ break;
+ case ConsoleKey.D4:
+ lrs.SkiaBitmapBenchmarkParallel();
+ break;
+ case ConsoleKey.D5:
+ lrs.NetVipsBenchmarkParallel();
+ break;
+ case ConsoleKey.D6:
+ lrs.MagickBenchmarkParallel();
+ break;
+ }
+
+ timer.Stop();
+ var stats = new Stats(timer, lrs.TotalProcessedMegapixels);
+ Console.WriteLine("Done. TotalProcessedMegapixels: " + lrs.TotalProcessedMegapixels);
+ Console.WriteLine(stats.GetMarkdown());
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex.ToString());
+ }
+ }
+
+ private struct Stats
+ {
+ public double TotalSeconds { get; }
+
+ public double TotalMegapixels { get; }
+
+ public double MegapixelsPerSec { get; }
+
+ public double MegapixelsPerSecPerCpu { get; }
+
+ public Stats(Stopwatch sw, double totalMegapixels)
+ {
+ 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(this.TotalSeconds)} | {nameof(this.MegapixelsPerSec)} | {nameof(this.MegapixelsPerSecPerCpu)} |");
+ bld.AppendLine(
+ $"| {L(nameof(this.TotalSeconds))} | {L(nameof(this.MegapixelsPerSec))} | {L(nameof(this.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);
+
+ private void ImageSharpBenchmarkParallel() => this.ForEachImage(this.benchmarks.ImageSharpResize);
+
+ private void MagickBenchmarkParallel() => this.ForEachImage(this.benchmarks.MagickResize);
+
+ private void MagicScalerBenchmarkParallel() => this.ForEachImage(this.benchmarks.MagicScalerResize);
+
+ 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..9dd7e4c820 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,13 +32,14 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox
///
public static void Main(string[] args)
{
- RunJpegEncoderProfilingTests();
+ LoadResizeSaveParallelMemoryStress.Run();
+ // RunJpegEncoderProfilingTests();
// RunJpegColorProfilingTests();
// RunDecodeJpegProfilingTests();
// RunToVector4ProfilingTest();
// RunResizeProfilingTest();
- Console.ReadLine();
+ // Console.ReadLine();
}
private static void RunJpegEncoderProfilingTests()
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 @@
+