Browse Source

introduce RunExperiment and fix warnings in test code

pull/3111/head
antonfirsov 1 month ago
parent
commit
57ebbff98e
  1. 2
      tests/Directory.Build.targets
  2. 18
      tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs
  3. 13
      tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs
  4. 88
      tests/ImageSharp.Tests.ProfilingSandbox/ParallelProcessingStress.Experiment.cs
  5. 38
      tests/ImageSharp.Tests.ProfilingSandbox/ParallelProcessingStress.cs
  6. 5
      tests/ImageSharp.Tests.ProfilingSandbox/Program.cs
  7. 14
      tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs

2
tests/Directory.Build.targets

@ -24,7 +24,7 @@
Do not update to 14+ yet. There's differnce in how the BMP decoder handles rounding in 16 bit images.
See https://github.com/ImageMagick/ImageMagick/commit/27a0a9c37f18af9c8d823a3ea076f600843b553c
-->
<PackageReference Update="Magick.NET-Q16-AnyCPU" Version="13.10.0" />
<PackageReference Update="Magick.NET-Q16-AnyCPU" Version="14.11.1" />
<PackageReference Update="Microsoft.DotNet.RemoteExecutor" Version="10.0.0-beta.25563.105" />
<PackageReference Update="Microsoft.DotNet.XUnitExtensions" Version="8.0.0-beta.23580.1" />
<PackageReference Update="Moq" Version="4.20.72" />

18
tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs

@ -6,6 +6,7 @@ using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using ImageMagick;
using PhotoSauce.MagicScaler;
using SixLabors.ImageSharp.Formats;
@ -27,7 +28,8 @@ public enum JpegKind
Any = Baseline | Progressive
}
public class LoadResizeSaveStressRunner
[SupportedOSPlatform("windows")]
public sealed class LoadResizeSaveStressRunner
{
private const int Quality = 75;
@ -158,7 +160,7 @@ public class LoadResizeSaveStressRunner
this.outputDirectory,
Path.GetFileNameWithoutExtension(inputPath) + "-" + postfix + Path.GetExtension(inputPath));
private (int Width, int Height) ScaledSize(int inWidth, int inHeight, int outSize)
private static (int Width, int Height) ScaledSize(int inWidth, int inHeight, int outSize)
{
int width, height;
if (inWidth > inHeight)
@ -180,7 +182,7 @@ public class LoadResizeSaveStressRunner
using SystemDrawingImage image = SystemDrawingImage.FromFile(input, true);
this.LogImageProcessed(image.Width, image.Height);
(int width, int height) = this.ScaledSize(image.Width, image.Height, this.ThumbnailSize);
(int width, int height) = ScaledSize(image.Width, image.Height, this.ThumbnailSize);
Bitmap resized = new(width, height);
using Graphics graphics = Graphics.FromImage(resized);
using ImageAttributes attributes = new();
@ -248,10 +250,10 @@ public class LoadResizeSaveStressRunner
public void MagickResize(string input)
{
using MagickImage image = new(input);
this.LogImageProcessed(image.Width, image.Height);
this.LogImageProcessed((int)image.Width, (int)image.Height);
// Resize it to fit a 150x150 square
image.Resize(this.ThumbnailSize, this.ThumbnailSize);
image.Resize((uint)this.ThumbnailSize, (uint)this.ThumbnailSize);
// Reduce the size of the file
image.Strip();
@ -282,7 +284,7 @@ public class LoadResizeSaveStressRunner
{
using SKBitmap original = SKBitmap.Decode(input);
this.LogImageProcessed(original.Width, original.Height);
(int width, int height) = this.ScaledSize(original.Width, original.Height, this.ThumbnailSize);
(int width, int height) = ScaledSize(original.Width, original.Height, this.ThumbnailSize);
using SKSurface surface = SKSurface.Create(new SKImageInfo(width, height, original.ColorType, original.AlphaType));
using SKPaint paint = new() { FilterQuality = SKFilterQuality.High };
SKCanvas canvas = surface.Canvas;
@ -300,7 +302,7 @@ public class LoadResizeSaveStressRunner
{
using SKBitmap original = SKBitmap.Decode(input);
this.LogImageProcessed(original.Width, original.Height);
(int width, int height) = this.ScaledSize(original.Width, original.Height, this.ThumbnailSize);
(int width, int height) = ScaledSize(original.Width, original.Height, this.ThumbnailSize);
using SKBitmap resized = original.Resize(new SKImageInfo(width, height), SKFilterQuality.High);
if (resized == null)
{
@ -319,7 +321,7 @@ public class LoadResizeSaveStressRunner
SKImageInfo info = codec.Info;
this.LogImageProcessed(info.Width, info.Height);
(int width, int height) = this.ScaledSize(info.Width, info.Height, this.ThumbnailSize);
(int width, int height) = ScaledSize(info.Width, info.Height, this.ThumbnailSize);
SKSizeI supportedScale = codec.GetScaledDimensions((float)width / info.Width);
using SKBitmap original = SKBitmap.Decode(codec, new SKImageInfo(supportedScale.Width, supportedScale.Height));

13
tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs

@ -3,6 +3,7 @@
using System.Diagnostics;
using System.Globalization;
using System.Runtime.Versioning;
using System.Text;
using CommandLine;
using CommandLine.Text;
@ -13,7 +14,8 @@ using SixLabors.ImageSharp.Memory.Internals;
namespace SixLabors.ImageSharp.Tests.ProfilingSandbox;
// See ImageSharp.Benchmarks/LoadResizeSave/README.md
internal class LoadResizeSaveParallelMemoryStress
[SupportedOSPlatform("windows")]
internal sealed class LoadResizeSaveParallelMemoryStress
{
private LoadResizeSaveParallelMemoryStress()
{
@ -206,14 +208,15 @@ internal class LoadResizeSaveParallelMemoryStress
StringBuilder bld = new();
bld.AppendLine($"| {nameof(this.TotalSeconds)} | {nameof(this.MegapixelsPerSec)} | {nameof(this.MegapixelsPerSecPerCpu)} |");
bld.AppendLine(
CultureInfo.InvariantCulture,
$"| {L(nameof(this.TotalSeconds))} | {L(nameof(this.MegapixelsPerSec))} | {L(nameof(this.MegapixelsPerSecPerCpu))} |");
bld.Append("| ");
bld.AppendFormat(F(nameof(this.TotalSeconds)), this.TotalSeconds);
bld.AppendFormat(CultureInfo.InvariantCulture, F(nameof(this.TotalSeconds)), this.TotalSeconds);
bld.Append(" | ");
bld.AppendFormat(F(nameof(this.MegapixelsPerSec)), this.MegapixelsPerSec);
bld.AppendFormat(CultureInfo.InvariantCulture, F(nameof(this.MegapixelsPerSec)), this.MegapixelsPerSec);
bld.Append(" | ");
bld.AppendFormat(F(nameof(this.MegapixelsPerSecPerCpu)), this.MegapixelsPerSecPerCpu);
bld.AppendFormat(CultureInfo.InvariantCulture, F(nameof(this.MegapixelsPerSecPerCpu)), this.MegapixelsPerSecPerCpu);
bld.AppendLine(" |");
return bld.ToString();
@ -223,7 +226,7 @@ internal class LoadResizeSaveParallelMemoryStress
}
}
private class CommandLineOptions
private sealed class CommandLineOptions
{
[Option('a', "async-imagesharp", Required = false, Default = false, HelpText = "Async ImageSharp without benchmark switching")]
public bool AsyncImageSharp { get; set; }

88
tests/ImageSharp.Tests.ProfilingSandbox/ParallelProcessingStress.Experiment.cs

@ -0,0 +1,88 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using CommandLine;
using CommandLine.Text;
namespace SixLabors.ImageSharp.Tests.ProfilingSandbox;
public partial class ParallelProcessingStress
{
public static void RunExperiment(string[] args)
{
ExperimentOptions options = null;
using Parser parser = new(settings => settings.CaseInsensitiveEnumValues = true);
ParserResult<ExperimentOptions> result = parser.ParseArguments<ExperimentOptions>(args).WithParsed(o => options = o);
if (options == null)
{
Console.WriteLine(HelpText.RenderUsageText(result));
return;
}
RunExperiment(options.Method, options.Seconds, options.IterationCount);
}
public static void RunExperiment(Method method, int seconds = 5, int times = 5)
{
// Warmup
Console.WriteLine("Warming up...");
CommandLineOptions warmupOptions = new() { Method = method, Seconds = 1 };
warmupOptions.Normalize();
new ParallelProcessingStress(warmupOptions).Run();
// Outer loop: run inner loop for each parallelism level
List<(int Parallelism, double AvgMpxPerSecPerCpu)> results = new();
foreach (int parallelism in ParallelismLevels())
{
Console.WriteLine($"\nRunning {method} with ProcessorParallelism={parallelism} ({times}x {seconds}s)...");
double totalMpxPerSecPerCpu = 0;
for (int i = 0; i < times; i++)
{
CommandLineOptions options = new() { Method = method, ProcessorParallelism = parallelism, Seconds = seconds };
options.Normalize();
Stats stats = new ParallelProcessingStress(options).Run();
totalMpxPerSecPerCpu += stats.MegapixelsPerSecPerCpu;
}
results.Add((parallelism, totalMpxPerSecPerCpu / times));
}
// Print results as markdown table
Console.WriteLine();
Console.WriteLine("| ProcessorParallelism | MegapixelsPerSecPerCpu |");
Console.WriteLine("|---------------------:|-----------------------:|");
foreach ((int parallelism, double avg) in results)
{
Console.WriteLine($"| {parallelism,20} | {avg,22:f3} |");
}
}
private sealed class ExperimentOptions
{
[Option('m', "method", Required = false, Default = Method.Edges, HelpText = "The stress test method to run (Edges, Crop)")]
public Method Method { get; set; } = Method.Edges;
[Option('s', "seconds", Required = false, Default = 5, HelpText = "Duration of each run in seconds")]
public int Seconds { get; set; } = 5;
[Option('i', "iterations", Required = false, Default = 5, HelpText = "Number of runs per parallelism level")]
public int IterationCount { get; set; } = 5;
}
private static IEnumerable<int> ParallelismLevels()
{
int cpuCount = Environment.ProcessorCount;
for (int p = 1; p <= cpuCount; p *= 2)
{
yield return p;
}
// When cpuCount is not a power of two, append it as the final step
if ((cpuCount & (cpuCount - 1)) != 0)
{
yield return cpuCount;
}
}
}

38
tests/ImageSharp.Tests.ProfilingSandbox/ParallelProcessingStress.cs

@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License.
using System.Diagnostics;
using System.Globalization;
using System.Text;
using CommandLine;
using CommandLine.Text;
@ -10,13 +11,13 @@ using SixLabors.ImageSharp.Processing;
namespace SixLabors.ImageSharp.Tests.ProfilingSandbox;
public class ParallelProcessingStress
public sealed partial class ParallelProcessingStress
{
private CommandLineOptions options;
private Configuration configuration;
private ulong totalKiloPixels;
public static Stats Run(string[] args)
public static void Run(string[] args)
{
CommandLineOptions options = null;
if (args.Length > 0)
@ -24,13 +25,13 @@ public class ParallelProcessingStress
options = CommandLineOptions.Parse(args);
if (options == null)
{
return null;
return;
}
}
options ??= new CommandLineOptions();
ParallelProcessingStress stress = new(options.Normalize());
return stress.Run();
stress.Run();
}
private ParallelProcessingStress(CommandLineOptions options)
@ -66,7 +67,7 @@ public class ParallelProcessingStress
stopwatch.Stop();
double totalMegaPixels = this.totalKiloPixels / 1000.0;
Stats stats = new(stopwatch, totalMegaPixels, systemOptions.MaxDegreeOfParallelism);
Stats stats = new(stopwatch.ElapsedMilliseconds, totalMegaPixels, systemOptions.MaxDegreeOfParallelism);
Console.WriteLine(stats.GetMarkdown());
return stats;
}
@ -96,7 +97,7 @@ public class ParallelProcessingStress
return image.Width * image.Height;
}
public record Stats
private sealed record Stats
{
public double TotalSeconds { get; }
@ -106,10 +107,10 @@ public class ParallelProcessingStress
public double MegapixelsPerSecPerCpu { get; }
public Stats(Stopwatch sw, double totalMegapixels, int cpuCount)
public Stats(long elapsedMilliseconds, double totalMegapixels, int cpuCount)
{
this.TotalMegapixels = totalMegapixels;
this.TotalSeconds = sw.ElapsedMilliseconds / 1000.0;
this.TotalSeconds = elapsedMilliseconds / 1000.0;
this.MegapixelsPerSec = totalMegapixels / this.TotalSeconds;
this.MegapixelsPerSecPerCpu = this.MegapixelsPerSec / cpuCount;
}
@ -117,16 +118,19 @@ public class ParallelProcessingStress
public string GetMarkdown()
{
StringBuilder bld = new();
bld.AppendLine($"| {nameof(this.TotalSeconds)} | {nameof(this.MegapixelsPerSec)} | {nameof(this.MegapixelsPerSecPerCpu)} |");
bld.AppendLine(
CultureInfo.InvariantCulture,
$"| {nameof(this.TotalSeconds)} | {nameof(this.MegapixelsPerSec)} | {nameof(this.MegapixelsPerSecPerCpu)} |");
bld.AppendLine(
CultureInfo.InvariantCulture,
$"| {L(nameof(this.TotalSeconds))} | {L(nameof(this.MegapixelsPerSec))} | {L(nameof(this.MegapixelsPerSecPerCpu))} |");
bld.Append("| ");
bld.AppendFormat(F(nameof(this.TotalSeconds)), this.TotalSeconds);
bld.AppendFormat(CultureInfo.InvariantCulture, F(nameof(this.TotalSeconds)), this.TotalSeconds);
bld.Append(" | ");
bld.AppendFormat(F(nameof(this.MegapixelsPerSec)), this.MegapixelsPerSec);
bld.AppendFormat(CultureInfo.InvariantCulture, F(nameof(this.MegapixelsPerSec)), this.MegapixelsPerSec);
bld.Append(" | ");
bld.AppendFormat(F(nameof(this.MegapixelsPerSecPerCpu)), this.MegapixelsPerSecPerCpu);
bld.AppendFormat(CultureInfo.InvariantCulture, F(nameof(this.MegapixelsPerSecPerCpu)), this.MegapixelsPerSecPerCpu);
bld.AppendLine(" |");
return bld.ToString();
@ -136,9 +140,13 @@ public class ParallelProcessingStress
}
}
private enum Method { Edges, Crop }
public enum Method
{
Edges,
Crop
}
private class CommandLineOptions
private sealed class CommandLineOptions
{
[Option('m', "method", Required = false, Default = Method.Edges, HelpText = "The stress test method to run (Edges, Crop)")]
public Method Method { get; set; } = Method.Edges;
@ -199,4 +207,4 @@ public class ParallelProcessingStress
return result;
}
}
}
}

5
tests/ImageSharp.Tests.ProfilingSandbox/Program.cs

@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox;
public class Program
{
private class ConsoleOutput : ITestOutputHelper
private sealed class ConsoleOutput : ITestOutputHelper
{
public void WriteLine(string message) => Console.WriteLine(message);
@ -33,7 +33,8 @@ public class Program
try
{
// LoadResizeSaveParallelMemoryStress.Run(args);
ParallelProcessingStress.Run(args);
ParallelProcessingStress.RunExperiment(args);
// ParallelProcessingStress.Run(args);
}
catch (Exception ex)
{

14
tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs

@ -58,14 +58,14 @@ public class MagickReferenceDecoder : ImageDecoder
MagickReadSettings settings = new()
{
FrameCount = (int)options.MaxFrames
FrameCount = options.MaxFrames
};
settings.SetDefines(bmpReadDefines);
settings.SetDefines(pngReadDefines);
using MagickImageCollection magickImageCollection = new(stream, settings);
int imageWidth = magickImageCollection.Max(x => x.Width);
int imageHeight = magickImageCollection.Max(x => x.Height);
int imageWidth = (int)magickImageCollection.Max(x => x.Width);
int imageHeight = (int)magickImageCollection.Max(x => x.Height);
List<ImageFrame<TPixel>> framesList = [];
foreach (IMagickImage<ushort> magicFrame in magickImageCollection)
@ -74,10 +74,10 @@ public class MagickReferenceDecoder : ImageDecoder
framesList.Add(frame);
Buffer2DRegion<TPixel> buffer = frame.PixelBuffer.GetRegion(
imageWidth - magicFrame.Width,
imageHeight - magicFrame.Height,
magicFrame.Width,
magicFrame.Height);
imageWidth - (int)magicFrame.Width,
imageHeight - (int)magicFrame.Height,
(int)magicFrame.Width,
(int)magicFrame.Height);
using IUnsafePixelCollection<ushort> pixels = magicFrame.GetPixelsUnsafe();
if (magicFrame.Depth is 12 or 10 or 8 or 6 or 5 or 4 or 3 or 2 or 1)

Loading…
Cancel
Save