mirror of https://github.com/SixLabors/ImageSharp
3 changed files with 260 additions and 6 deletions
@ -0,0 +1,56 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using BenchmarkDotNet.Attributes; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
using SixLabors.ImageSharp.Processing; |
||||
|
using SixLabors.ImageSharp.Tests; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Benchmarks; |
||||
|
|
||||
|
public class ParallelProcessing |
||||
|
{ |
||||
|
private Image<Rgba32> image; |
||||
|
private Configuration configuration; |
||||
|
|
||||
|
public static IEnumerable<int> MaxDegreeOfParallelismValues() |
||||
|
{ |
||||
|
int processorCount = Environment.ProcessorCount; |
||||
|
for (int p = 1; p <= processorCount; p *= 2) |
||||
|
{ |
||||
|
yield return p; |
||||
|
} |
||||
|
|
||||
|
if ((processorCount & (processorCount - 1)) != 0) |
||||
|
{ |
||||
|
yield return processorCount; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[ParamsSource(nameof(MaxDegreeOfParallelismValues))] |
||||
|
public int MaxDegreeOfParallelism { get; set; } |
||||
|
|
||||
|
[GlobalSetup] |
||||
|
public void Setup() |
||||
|
{ |
||||
|
this.image = new Image<Rgba32>(2048, 2048); |
||||
|
this.configuration = Configuration.Default.Clone(); |
||||
|
this.configuration.MaxDegreeOfParallelism = this.MaxDegreeOfParallelism; |
||||
|
} |
||||
|
|
||||
|
[Benchmark] |
||||
|
public void DetectEdges() => this.image.Mutate(this.configuration, x => x.DetectEdges()); |
||||
|
|
||||
|
[Benchmark] |
||||
|
public void Crop() |
||||
|
{ |
||||
|
Rectangle bounds = this.image.Bounds; |
||||
|
bounds = new Rectangle(1, 1, bounds.Width - 2, bounds.Height - 2); |
||||
|
this.image |
||||
|
.Clone(this.configuration, x => x.Crop(bounds)) |
||||
|
.Dispose(); |
||||
|
} |
||||
|
|
||||
|
[GlobalCleanup] |
||||
|
public void Cleanup() => this.image.Dispose(); |
||||
|
} |
||||
@ -0,0 +1,202 @@ |
|||||
|
// Copyright (c) Six Labors.
|
||||
|
// Licensed under the Six Labors Split License.
|
||||
|
|
||||
|
using System.Diagnostics; |
||||
|
using System.Text; |
||||
|
using CommandLine; |
||||
|
using CommandLine.Text; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
using SixLabors.ImageSharp.Processing; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Tests.ProfilingSandbox; |
||||
|
|
||||
|
public class ParallelProcessingStress |
||||
|
{ |
||||
|
private CommandLineOptions options; |
||||
|
private Configuration configuration; |
||||
|
private ulong totalKiloPixels; |
||||
|
|
||||
|
public static Stats Run(string[] args) |
||||
|
{ |
||||
|
CommandLineOptions options = null; |
||||
|
if (args.Length > 0) |
||||
|
{ |
||||
|
options = CommandLineOptions.Parse(args); |
||||
|
if (options == null) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
options ??= new CommandLineOptions(); |
||||
|
ParallelProcessingStress stress = new(options.Normalize()); |
||||
|
return stress.Run(); |
||||
|
} |
||||
|
|
||||
|
private ParallelProcessingStress(CommandLineOptions options) |
||||
|
{ |
||||
|
this.options = options; |
||||
|
this.configuration = Configuration.Default.Clone(); |
||||
|
this.configuration.MaxDegreeOfParallelism = options.ProcessorParallelism > 0 |
||||
|
? options.ProcessorParallelism |
||||
|
: Environment.ProcessorCount; |
||||
|
} |
||||
|
|
||||
|
private Stats Run() |
||||
|
{ |
||||
|
ParallelOptions systemOptions = new() { MaxDegreeOfParallelism = this.options.SystemParallelism }; |
||||
|
Func<int> action = this.options.Method switch |
||||
|
{ |
||||
|
Method.Crop => this.Crop, |
||||
|
_ => this.DetectEdges, |
||||
|
}; |
||||
|
Console.WriteLine($"Running {this.options.Method} for {this.options.Seconds} seconds ..."); |
||||
|
Stopwatch stopwatch = Stopwatch.StartNew(); |
||||
|
TimeSpan runFor = TimeSpan.FromSeconds(this.options.Seconds); |
||||
|
Parallel.ForEach(InfiniteSequence(), systemOptions, (_, state) => |
||||
|
{ |
||||
|
ulong kiloPixels = (ulong)action() / 1000; |
||||
|
Interlocked.Add(ref this.totalKiloPixels, kiloPixels); |
||||
|
|
||||
|
if (stopwatch.Elapsed >= runFor) |
||||
|
{ |
||||
|
state.Stop(); |
||||
|
} |
||||
|
}); |
||||
|
stopwatch.Stop(); |
||||
|
|
||||
|
double totalMegaPixels = this.totalKiloPixels / 1000.0; |
||||
|
Stats stats = new(stopwatch, totalMegaPixels, systemOptions.MaxDegreeOfParallelism); |
||||
|
Console.WriteLine(stats.GetMarkdown()); |
||||
|
return stats; |
||||
|
} |
||||
|
|
||||
|
private static IEnumerable<long> InfiniteSequence() |
||||
|
{ |
||||
|
long i = 0; |
||||
|
while (true) |
||||
|
{ |
||||
|
yield return i++; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private int DetectEdges() |
||||
|
{ |
||||
|
using Image<Rgba32> image = new(this.options.Width, this.options.Height); |
||||
|
image.Mutate(this.configuration, x => x.DetectEdges()); |
||||
|
return image.Width * image.Height; |
||||
|
} |
||||
|
|
||||
|
private int Crop() |
||||
|
{ |
||||
|
using Image<Rgba32> image = new(this.options.Width, this.options.Height); |
||||
|
Rectangle bounds = image.Bounds; |
||||
|
bounds = new Rectangle(1, 1, bounds.Width - 2, bounds.Height - 2); |
||||
|
image.Clone(this.configuration, x => x.Crop(bounds)).Dispose(); |
||||
|
return image.Width * image.Height; |
||||
|
} |
||||
|
|
||||
|
public record Stats |
||||
|
{ |
||||
|
public double TotalSeconds { get; } |
||||
|
|
||||
|
public double TotalMegapixels { get; } |
||||
|
|
||||
|
public double MegapixelsPerSec { get; } |
||||
|
|
||||
|
public double MegapixelsPerSecPerCpu { get; } |
||||
|
|
||||
|
public Stats(Stopwatch sw, double totalMegapixels, int cpuCount) |
||||
|
{ |
||||
|
this.TotalMegapixels = totalMegapixels; |
||||
|
this.TotalSeconds = sw.ElapsedMilliseconds / 1000.0; |
||||
|
this.MegapixelsPerSec = totalMegapixels / this.TotalSeconds; |
||||
|
this.MegapixelsPerSecPerCpu = this.MegapixelsPerSec / cpuCount; |
||||
|
} |
||||
|
|
||||
|
public string GetMarkdown() |
||||
|
{ |
||||
|
StringBuilder bld = new(); |
||||
|
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 enum Method { Edges, Crop } |
||||
|
|
||||
|
private 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; |
||||
|
|
||||
|
[Option('p', "processor-parallelism", Required = false, Default = -1, HelpText = "Level of parallelism for the image processor")] |
||||
|
public int ProcessorParallelism { get; set; } = -1; |
||||
|
|
||||
|
[Option('t', "system-parallelism", Required = false, Default = -1, HelpText = "Level of parallelism for the outer loop")] |
||||
|
public int SystemParallelism { get; set; } = -1; |
||||
|
|
||||
|
[Option('w', "width", Required = false, Default = 4000, HelpText = "Width of the test image")] |
||||
|
public int Width { get; set; } = 4000; |
||||
|
|
||||
|
[Option('h', "height", Required = false, Default = 4000, HelpText = "Height of the test image")] |
||||
|
public int Height { get; set; } = 4000; |
||||
|
|
||||
|
[Option('s', "seconds", Required = false, Default = 5, HelpText = "Duration of the stress test in seconds")] |
||||
|
public int Seconds { get; set; } = 5; |
||||
|
|
||||
|
public override string ToString() => string.Join( |
||||
|
Environment.NewLine, |
||||
|
$"method: {this.Method}", |
||||
|
$"processor-parallelism: {this.ProcessorParallelism}", |
||||
|
$"system-parallelism: {this.SystemParallelism}", |
||||
|
$"width: {this.Width}", |
||||
|
$"height: {this.Height}", |
||||
|
$"seconds: {this.Seconds}"); |
||||
|
|
||||
|
public CommandLineOptions Normalize() |
||||
|
{ |
||||
|
if (this.ProcessorParallelism < 0) |
||||
|
{ |
||||
|
this.ProcessorParallelism = Environment.ProcessorCount; |
||||
|
} |
||||
|
|
||||
|
if (this.SystemParallelism < 0) |
||||
|
{ |
||||
|
this.SystemParallelism = Environment.ProcessorCount; |
||||
|
} |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
public static CommandLineOptions Parse(string[] args) |
||||
|
{ |
||||
|
CommandLineOptions result = null; |
||||
|
using Parser parser = new(settings => settings.CaseInsensitiveEnumValues = true); |
||||
|
ParserResult<CommandLineOptions> parserResult = parser.ParseArguments<CommandLineOptions>(args).WithParsed(o => |
||||
|
{ |
||||
|
result = o; |
||||
|
}); |
||||
|
|
||||
|
if (result == null) |
||||
|
{ |
||||
|
Console.WriteLine(HelpText.RenderUsageText(parserResult)); |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue