Browse Source

unify parallel & non-parallel benchmarks

pull/1687/head
Anton Firszov 5 years ago
parent
commit
7a91493ddb
  1. 72
      tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs
  2. 26
      tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs
  3. 52
      tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStress_NonParallel.cs
  4. 48
      tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStress_Parallel.cs
  5. 2
      tests/ImageSharp.Benchmarks/LoadResizeSave/README.md
  6. 59
      tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs

72
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<string> 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);
}
}

26
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<string> 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)

52
tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStress_NonParallel.cs

@ -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<string> 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);
}
}

48
tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStress_Parallel.cs

@ -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<string> 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);
}
}

2
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 `<solution-dir>\tests\Images\ActualOutput\MemoryStress\`.

59
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<string> 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<string> 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
{
}
}

Loading…
Cancel
Save