From 7a91493ddb05332579c989be00df10e69cc3d353 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 4 Jul 2021 18:31:03 +0200 Subject: [PATCH] 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 + { + } +}