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