Browse Source

stress testing improvements

pull/1730/head
Anton Firszov 4 years ago
parent
commit
b43e963b60
  1. 31
      tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs
  2. 70
      tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs
  3. 7
      tests/ImageSharp.Tests/RunTestsInLoop.ps1

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

@ -44,6 +44,8 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave
public double TotalProcessedMegapixels { get; private set; }
public Size LastProcessedImageSize { get; private set; }
private string outputDirectory;
public int ImageCount { get; set; } = int.MaxValue;
@ -54,9 +56,6 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave
public int ThumbnailSize { get; set; } = 150;
// Inject leaking memory allocation requests to ImageSharp processing code to stress-test finalizer behavior.
public bool EmulateLeakedAllocations { get; set; }
private static readonly string[] ProgressiveFiles =
{
"ancyloscelis-apiformis-m-paraguay-face_2014-08-08-095255-zs-pmax_15046500892_o.jpg",
@ -125,8 +124,9 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave
new ParallelOptions { MaxDegreeOfParallelism = this.MaxDegreeOfParallelism },
action);
private void IncreaseTotalMegapixels(int width, int height)
private void LogImageProcessed(int width, int height)
{
this.LastProcessedImageSize = new Size(width, height);
double pixels = width * (double)height;
this.TotalProcessedMegapixels += pixels / 1_000_000.0;
}
@ -156,7 +156,7 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave
public void SystemDrawingResize(string input)
{
using var image = SystemDrawingImage.FromFile(input, true);
this.IncreaseTotalMegapixels(image.Width, image.Height);
this.LogImageProcessed(image.Width, image.Height);
(int Width, int Height) scaled = this.ScaledSize(image.Width, image.Height, this.ThumbnailSize);
var resized = new Bitmap(scaled.Width, scaled.Height);
@ -182,13 +182,7 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave
// Resize it to fit a 150x150 square
using var image = ImageSharpImage.Load(input);
this.IncreaseTotalMegapixels(image.Width, image.Height);
if (this.EmulateLeakedAllocations)
{
_ = Configuration.Default.MemoryAllocator.Allocate<long>(image.Width * image.Height);
_ = Configuration.Default.MemoryAllocator.Allocate<byte>(1 << 21);
}
this.LogImageProcessed(image.Width, image.Height);
image.Mutate(i => i.Resize(new ResizeOptions
{
@ -201,17 +195,12 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave
// Save the results
image.Save(output, this.imageSharpJpegEncoder);
if (this.EmulateLeakedAllocations)
{
_ = Configuration.Default.MemoryAllocator.Allocate2D<long>(image.Width, image.Height);
}
}
public void MagickResize(string input)
{
using var image = new MagickImage(input);
this.IncreaseTotalMegapixels(image.Width, image.Height);
this.LogImageProcessed(image.Width, image.Height);
// Resize it to fit a 150x150 square
image.Resize(this.ThumbnailSize, this.ThumbnailSize);
@ -246,7 +235,7 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave
public void SkiaCanvasResize(string input)
{
using var original = SKBitmap.Decode(input);
this.IncreaseTotalMegapixels(original.Width, original.Height);
this.LogImageProcessed(original.Width, original.Height);
(int Width, int Height) scaled = this.ScaledSize(original.Width, original.Height, this.ThumbnailSize);
using var surface = SKSurface.Create(new SKImageInfo(scaled.Width, scaled.Height, original.ColorType, original.AlphaType));
using var paint = new SKPaint() { FilterQuality = SKFilterQuality.High };
@ -264,7 +253,7 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave
public void SkiaBitmapResize(string input)
{
using var original = SKBitmap.Decode(input);
this.IncreaseTotalMegapixels(original.Width, original.Height);
this.LogImageProcessed(original.Width, original.Height);
(int Width, int Height) scaled = this.ScaledSize(original.Width, original.Height, this.ThumbnailSize);
using var resized = original.Resize(new SKImageInfo(scaled.Width, scaled.Height), SKFilterQuality.High);
if (resized == null)
@ -283,7 +272,7 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave
using var codec = SKCodec.Create(input);
SKImageInfo info = codec.Info;
this.IncreaseTotalMegapixels(info.Width, info.Height);
this.LogImageProcessed(info.Width, info.Height);
(int Width, int Height) scaled = this.ScaledSize(info.Width, info.Height, this.ThumbnailSize);
SKSizeI supportedScale = codec.GetScaledDimensions((float)scaled.Width / info.Width);

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

@ -5,6 +5,7 @@ using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using CommandLine;
@ -27,13 +28,19 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox
this.Benchmarks.Init();
}
private int gcFrequency;
private int leakFrequency;
private int imageCounter;
public LoadResizeSaveStressRunner Benchmarks { get; }
public static void Run(string[] args)
{
Console.WriteLine($"Running: {typeof(LoadResizeSaveParallelMemoryStress).Assembly.Location}");
Console.WriteLine($"64 bit: {Environment.Is64BitProcess}");
var options = args.Length > 0 ? CommandLineOptions.Parse(args) : null;
CommandLineOptions options = args.Length > 0 ? CommandLineOptions.Parse(args) : null;
var lrs = new LoadResizeSaveParallelMemoryStress();
if (options != null)
@ -55,10 +62,12 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox
if (!options.KeepDefaultAllocator)
{
MemoryAllocator.Default = Configuration.Default.MemoryAllocator = options.CreateMemoryAllocator();
Configuration.Default.MemoryAllocator = options.CreateMemoryAllocator();
}
lrs.Benchmarks.EmulateLeakedAllocations = options.LeakAllocations;
lrs.leakFrequency = options.LeakFrequency;
lrs.gcFrequency = options.GcFrequency;
timer = Stopwatch.StartNew();
try
@ -80,16 +89,23 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox
Configuration.Default.MemoryAllocator.ReleaseRetainedResources();
}
for (int i = 0; i < options.FinalGcCount; i++)
int finalGcCount = -Math.Min(0, options.GcFrequency);
if (finalGcCount > 0)
{
GC.Collect();
GC.WaitForPendingFinalizers();
Thread.Sleep(1000);
Console.WriteLine($"TotalOutstandingHandles: {UnmanagedMemoryHandle.TotalOutstandingHandles}");
Console.WriteLine($"GC x {finalGcCount}, with 3 seconds wait.");
for (int i = 0; i < finalGcCount; i++)
{
Thread.Sleep(3000);
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
}
var stats = new Stats(timer, lrs.Benchmarks.TotalProcessedMegapixels);
Console.WriteLine($"Total Megapixels: {stats.TotalMegapixels}, TotalOomRetries: {UnmanagedMemoryHandle.TotalOomRetries}");
Console.WriteLine($"Total Megapixels: {stats.TotalMegapixels}, TotalOomRetries: {UnmanagedMemoryHandle.TotalOomRetries}, TotalOutstandingHandles: {UnmanagedMemoryHandle.TotalOutstandingHandles}, Total Gen2 GC count: {GC.CollectionCount(2)}");
Console.WriteLine(stats.GetMarkdown());
if (options?.FileOutput != null)
{
@ -121,8 +137,9 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox
2. ImageSharp
3. MagicScaler
4. SkiaSharp
5. NetVips
6. ImageMagick
5. SkiaSharp - Decode to target size
6. NetVips
7. ImageMagick
");
ConsoleKey key = Console.ReadKey().Key;
@ -149,9 +166,12 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox
lrs.SkiaBitmapBenchmarkParallel();
break;
case ConsoleKey.D5:
lrs.NetVipsBenchmarkParallel();
lrs.SkiaBitmapDecodeToTargetSizeBenchmarkParallel();
break;
case ConsoleKey.D6:
lrs.NetVipsBenchmarkParallel();
break;
case ConsoleKey.D7:
lrs.MagickBenchmarkParallel();
break;
}
@ -222,8 +242,8 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox
[Option('r', "repeat-count", Required = false, Default = 1, HelpText = "Times to run the whole benchmark")]
public int RepeatCount { get; set; } = 1;
[Option('g', "final-gc-count", Required = false, Default = 0, HelpText = "How many times to GC.Collect after execution")]
public int FinalGcCount { get; set; }
[Option('g', "gc-frequency", Required = false, Default = 0, HelpText = "Positive number: call GC every 'g'-th resize. Negative number: call GC '-g' times in the end.")]
public int GcFrequency { get; set; }
[Option('e', "release-at-end", Required = false, Default = false, HelpText = "Specify to run ReleaseRetainedResources() after execution")]
public bool ReleaseRetainedResourcesAtEnd { get; set; }
@ -237,8 +257,8 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox
[Option('t', "trim-period", Required = false, Default = null, HelpText = "Trim period for the pool in seconds")]
public int? TrimTimeSeconds { get; set; }
[Option('l', "leak-allocations", Required = false, Default = false, HelpText = "Inject leaking memory allocation requests to stress-test finalizer behavior.")]
public bool LeakAllocations { get; set; }
[Option('l', "leak-frequency", Required = false, Default = 0, HelpText = "Inject leaking memory allocations after every 'l'-th resize to stress test finalizer behavior.")]
public int LeakFrequency { get; set; }
public static CommandLineOptions Parse(string[] args)
{
@ -257,7 +277,7 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox
}
public override string ToString() =>
$"p({this.MaxDegreeOfParallelism})_i({this.ImageSharp})_d({this.KeepDefaultAllocator})_m({this.MaxContiguousPoolBufferMegaBytes})_s({this.MaxPoolSizeMegaBytes})_u({this.MaxCapacityOfNonPoolBuffersMegaBytes})_r({this.RepeatCount})_g({this.FinalGcCount})_e({this.ReleaseRetainedResourcesAtEnd}_l({this.LeakAllocations}))";
$"p({this.MaxDegreeOfParallelism})_i({this.ImageSharp})_d({this.KeepDefaultAllocator})_m({this.MaxContiguousPoolBufferMegaBytes})_s({this.MaxPoolSizeMegaBytes})_u({this.MaxCapacityOfNonPoolBuffersMegaBytes})_r({this.RepeatCount})_g({this.GcFrequency})_e({this.ReleaseRetainedResourcesAtEnd})_l({this.LeakFrequency})";
public MemoryAllocator CreateMemoryAllocator()
{
@ -290,28 +310,32 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox
private void SystemDrawingBenchmarkParallel() => this.ForEachImage(this.Benchmarks.SystemDrawingResize);
private void ImageSharpBenchmarkParallel()
{
int cnt = 0;
private void ImageSharpBenchmarkParallel() =>
this.ForEachImage(f =>
{
int cnt = Interlocked.Increment(ref this.imageCounter);
this.Benchmarks.ImageSharpResize(f);
if (cnt % 4 == 0 && this.Benchmarks.EmulateLeakedAllocations)
if (this.leakFrequency > 0 && cnt % this.leakFrequency == 0)
{
_ = Configuration.Default.MemoryAllocator.Allocate<byte>(1 << 16);
Size size = this.Benchmarks.LastProcessedImageSize;
_ = new Image<ImageSharp.PixelFormats.Rgba64>(size.Width, size.Height);
}
if (this.gcFrequency > 0 && cnt % this.gcFrequency == 0)
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
}
cnt++;
});
}
private void MagickBenchmarkParallel() => this.ForEachImage(this.Benchmarks.MagickResize);
private void MagicScalerBenchmarkParallel() => this.ForEachImage(this.Benchmarks.MagicScalerResize);
private void SkiaBitmapBenchmarkParallel() => this.ForEachImage(this.Benchmarks.SkiaBitmapResize);
private void SkiaBitmapDecodeToTargetSizeBenchmarkParallel() => this.ForEachImage(this.Benchmarks.SkiaBitmapDecodeToTargetSize);
private void NetVipsBenchmarkParallel() => this.ForEachImage(this.Benchmarks.NetVipsResize);
}

7
tests/ImageSharp.Tests/RunTestsInLoop.ps1

@ -1,16 +1,17 @@
# This script can be used to collect logs from sporadic bugs
Param(
[int]$TestRunCount=10,
[string]$TargetFramework="netcoreapp3.1"
[string]$TargetFramework="netcoreapp3.1",
[string]$Configuration="Release"
)
$runId = Get-Random -Minimum 0 -Maximum 9999
dotnet build -c Release -f $TargetFramework
dotnet build -c $Configuration -f $TargetFramework
for ($i = 0; $i -lt $TestRunCount; $i++) {
$logFile = ".\_testlog-" + $runId.ToString("d4") + "-run-" + $i.ToString("d3") + ".log"
Write-Host "Test run $i ..."
& dotnet test --no-build -c Release -f $TargetFramework 3>&1 2>&1 > $logFile
& dotnet test --no-build -c $Configuration -f $TargetFramework 3>&1 2>&1 > $logFile
if ($LastExitCode -eq 0) {
Write-Host "Success!"
Remove-Item $logFile

Loading…
Cancel
Save