Add image processing benchmarks and UI folder open buttons
- Added MaddoShared.Benchmarks project with BenchmarkDotNet for comprehensive image processing performance tests (parallel, chunk, size, stress). - Included helper for generating test images and custom configs to ensure InProcess toolchain for .NET Windows compatibility. - Added cross-platform scripts to run benchmarks easily. - Updated .gitignore for benchmark artifacts and temp files. - Exposed GetFilesToProcessPublic in ImageCreationStuff for testability. - Added file name sanitization in ImageCreatorSharp to prevent IO errors. - Enhanced WinForms UI: added "Open" buttons for source/destination folders, handled folder opening in Explorer, and improved user messaging and layout. - Updated solution file to include new benchmark project.
This commit is contained in:
parent
39b0904a72
commit
c2fd4bf780
17 changed files with 1608 additions and 301 deletions
25
MaddoShared.Benchmarks/.gitignore
vendored
Normal file
25
MaddoShared.Benchmarks/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# BenchmarkDotNet artifacts
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# Test images generated during benchmarks
|
||||
TestImages/
|
||||
|
||||
# Build outputs
|
||||
bin/
|
||||
obj/
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# Results and logs
|
||||
*.log
|
||||
*.html
|
||||
*.csv
|
||||
results/
|
||||
|
||||
# Temporary files
|
||||
*.tmp
|
||||
*.temp
|
||||
91
MaddoShared.Benchmarks/BenchmarkConfig.cs
Normal file
91
MaddoShared.Benchmarks/BenchmarkConfig.cs
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
using BenchmarkDotNet.Columns;
|
||||
using BenchmarkDotNet.Configs;
|
||||
using BenchmarkDotNet.Diagnosers;
|
||||
using BenchmarkDotNet.Engines;
|
||||
using BenchmarkDotNet.Exporters;
|
||||
using BenchmarkDotNet.Exporters.Csv;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using BenchmarkDotNet.Loggers;
|
||||
using BenchmarkDotNet.Toolchains.InProcess.Emit;
|
||||
|
||||
namespace MaddoShared.Benchmarks;
|
||||
|
||||
/// <summary>
|
||||
/// InProcess configuration for benchmarks requiring Windows-specific APIs
|
||||
/// This avoids the net10.0-windows vs net10.0 compatibility issue
|
||||
/// </summary>
|
||||
public class InProcessConfig : ManualConfig
|
||||
{
|
||||
public InProcessConfig()
|
||||
{
|
||||
AddLogger(ConsoleLogger.Default);
|
||||
AddExporter(HtmlExporter.Default);
|
||||
AddExporter(MarkdownExporter.GitHub);
|
||||
AddExporter(CsvExporter.Default);
|
||||
AddDiagnoser(MemoryDiagnoser.Default);
|
||||
|
||||
// Add job with InProcess toolchain
|
||||
AddJob(Job.Default
|
||||
.WithToolchain(InProcessEmitToolchain.Instance)
|
||||
.WithWarmupCount(1)
|
||||
.WithIterationCount(3));
|
||||
|
||||
// Configuration options
|
||||
WithOptions(ConfigOptions.DisableOptimizationsValidator);
|
||||
WithOptions(ConfigOptions.KeepBenchmarkFiles);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom configuration for image processing benchmarks
|
||||
/// Uses InProcess toolchain to avoid net10.0-windows compatibility issues
|
||||
/// </summary>
|
||||
public class BenchmarkConfig : ManualConfig
|
||||
{
|
||||
public BenchmarkConfig()
|
||||
{
|
||||
// Add console logger
|
||||
AddLogger(ConsoleLogger.Default);
|
||||
|
||||
// Add exporters for different formats
|
||||
AddExporter(HtmlExporter.Default);
|
||||
AddExporter(MarkdownExporter.GitHub);
|
||||
AddExporter(CsvExporter.Default);
|
||||
AddExporter(RPlotExporter.Default);
|
||||
|
||||
// Add diagnosers
|
||||
AddDiagnoser(MemoryDiagnoser.Default);
|
||||
AddDiagnoser(ThreadingDiagnoser.Default);
|
||||
|
||||
// Add columns
|
||||
AddColumn(StatisticColumn.Mean);
|
||||
AddColumn(StatisticColumn.StdDev);
|
||||
AddColumn(StatisticColumn.Error);
|
||||
AddColumn(StatisticColumn.Min);
|
||||
AddColumn(StatisticColumn.Max);
|
||||
AddColumn(StatisticColumn.Median);
|
||||
AddColumn(BaselineRatioColumn.RatioMean);
|
||||
|
||||
// Customize jobs with InProcess toolchain for Windows compatibility
|
||||
AddJob(Job.Default
|
||||
.WithToolchain(InProcessEmitToolchain.Instance)
|
||||
.WithWarmupCount(1)
|
||||
.WithIterationCount(3)
|
||||
.WithId("Quick"));
|
||||
|
||||
AddJob(Job.Default
|
||||
.WithToolchain(InProcessEmitToolchain.Instance)
|
||||
.WithWarmupCount(2)
|
||||
.WithIterationCount(5)
|
||||
.WithId("Standard"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fast configuration for development and quick tests
|
||||
/// </summary>
|
||||
public static IConfig Fast => new ManualConfig()
|
||||
.AddLogger(ConsoleLogger.Default)
|
||||
.AddExporter(MarkdownExporter.GitHub)
|
||||
.AddDiagnoser(MemoryDiagnoser.Default)
|
||||
.AddJob(Job.Dry.WithToolchain(InProcessEmitToolchain.Instance)); // Very fast, but less accurate
|
||||
}
|
||||
130
MaddoShared.Benchmarks/ChunkSizeBenchmarks.cs
Normal file
130
MaddoShared.Benchmarks/ChunkSizeBenchmarks.cs
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Configs;
|
||||
using BenchmarkDotNet.Engines;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using BenchmarkDotNet.Toolchains.InProcess.Emit;
|
||||
using MaddoShared.Benchmarks.Helpers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MaddoShared.Benchmarks;
|
||||
|
||||
/// <summary>
|
||||
/// Benchmarks focused on different chunk sizes for parallel processing
|
||||
/// </summary>
|
||||
[MemoryDiagnoser]
|
||||
[Config(typeof(InProcessConfig))]
|
||||
public class ChunkSizeBenchmarks
|
||||
{
|
||||
private string _sourceDirectory;
|
||||
private string _destinationDirectory;
|
||||
private ImageCreationStuff _imageCreationStuff;
|
||||
private PicSettings _picSettings;
|
||||
|
||||
[Params(100)]
|
||||
public int ImageCount { get; set; }
|
||||
|
||||
[Params(0, 5, 10, 20, 50)]
|
||||
public int ChunkSize { get; set; }
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
var tempBase = Path.Combine(Path.GetTempPath(), "ChunkBenchmarks", Guid.NewGuid().ToString());
|
||||
_sourceDirectory = Path.Combine(tempBase, "Source");
|
||||
_destinationDirectory = Path.Combine(tempBase, "Destination");
|
||||
|
||||
Directory.CreateDirectory(_sourceDirectory);
|
||||
Directory.CreateDirectory(_destinationDirectory);
|
||||
|
||||
Console.WriteLine($"Generating {ImageCount} test images for chunk size testing...");
|
||||
TestImageGenerator.GenerateTestImages(_sourceDirectory, ImageCount, width: 2000, height: 1500);
|
||||
|
||||
var loggerFactory = LoggerFactory.Create(builder =>
|
||||
{
|
||||
builder.SetMinimumLevel(LogLevel.Warning);
|
||||
});
|
||||
|
||||
var logger = loggerFactory.CreateLogger<ImageCreationStuff>();
|
||||
var imageCreatorLogger = loggerFactory.CreateLogger<ImageCreatorSharp>();
|
||||
|
||||
_picSettings = new PicSettings
|
||||
{
|
||||
DirectorySorgente = _sourceDirectory,
|
||||
DirectoryDestinazione = _destinationDirectory,
|
||||
DimStandard = 800,
|
||||
DimStandardMiniatura = 200,
|
||||
LarghezzaBig = 1024,
|
||||
AltezzaBig = 768,
|
||||
LarghezzaSmall = 200,
|
||||
AltezzaSmall = 150,
|
||||
CreaMiniature = true,
|
||||
AggiungiScritteMiniature = false,
|
||||
UsaForzaJpg = true,
|
||||
UsaRotazioneAutomatica = true,
|
||||
LogoAggiungi = false,
|
||||
FotoGrandeDimOrigina = false,
|
||||
TestoNome = false,
|
||||
NomeData = false,
|
||||
Suffisso = "_small",
|
||||
Margine = 10,
|
||||
Trasparenza = 100
|
||||
};
|
||||
|
||||
var imageCreatorService = new ImageCreatorSharp(_picSettings, imageCreatorLogger);
|
||||
_imageCreationStuff = new ImageCreationStuff(logger, _picSettings, imageCreatorService);
|
||||
}
|
||||
|
||||
[GlobalCleanup]
|
||||
public void Cleanup()
|
||||
{
|
||||
try
|
||||
{
|
||||
var tempBase = Path.GetDirectoryName(_sourceDirectory);
|
||||
if (Directory.Exists(tempBase))
|
||||
{
|
||||
Directory.Delete(tempBase, recursive: true);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Cleanup error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
[IterationSetup]
|
||||
public void IterationSetup()
|
||||
{
|
||||
if (Directory.Exists(_destinationDirectory))
|
||||
{
|
||||
Directory.Delete(_destinationDirectory, recursive: true);
|
||||
}
|
||||
Directory.CreateDirectory(_destinationDirectory);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task ProcessWithVariableChunkSize()
|
||||
{
|
||||
var options = new ImageCreationStuff.Options
|
||||
{
|
||||
SourcePath = _sourceDirectory,
|
||||
DestinationPath = _destinationDirectory,
|
||||
MaxThreads = Environment.ProcessorCount,
|
||||
ChunksSize = ChunkSize,
|
||||
LinearExecution = false,
|
||||
AggiornaSottodirectory = false,
|
||||
CreaSottocartelle = false,
|
||||
FilePerCartella = 100,
|
||||
SuffissoCartelle = "",
|
||||
CifreContatore = 4,
|
||||
NumerazioneType = NumerazioneType.Progressiva
|
||||
};
|
||||
|
||||
var results = new ConcurrentBag<string>();
|
||||
await _imageCreationStuff.ProcessImagesParallel(options, results, null, CancellationToken.None);
|
||||
}
|
||||
}
|
||||
107
MaddoShared.Benchmarks/Helpers/TestImageGenerator.cs
Normal file
107
MaddoShared.Benchmarks/Helpers/TestImageGenerator.cs
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
|
||||
namespace MaddoShared.Benchmarks.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Helper class to generate test images for benchmarking
|
||||
/// </summary>
|
||||
[SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")]
|
||||
public static class TestImageGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates a set of test JPEG images in the specified directory
|
||||
/// </summary>
|
||||
/// <param name="outputDirectory">Directory where images will be created</param>
|
||||
/// <param name="imageCount">Number of images to generate</param>
|
||||
/// <param name="width">Width of each image</param>
|
||||
/// <param name="height">Height of each image</param>
|
||||
/// <param name="includeSubfolders">Whether to create images in subfolders</param>
|
||||
public static void GenerateTestImages(
|
||||
string outputDirectory,
|
||||
int imageCount,
|
||||
int width = 4000,
|
||||
int height = 3000,
|
||||
bool includeSubfolders = false)
|
||||
{
|
||||
Directory.CreateDirectory(outputDirectory);
|
||||
|
||||
var random = new Random(42); // Fixed seed for reproducibility
|
||||
|
||||
for (int i = 0; i < imageCount; i++)
|
||||
{
|
||||
var targetDir = outputDirectory;
|
||||
|
||||
if (includeSubfolders && i % 10 == 0)
|
||||
{
|
||||
targetDir = Path.Combine(outputDirectory, $"Subfolder_{i / 10}");
|
||||
Directory.CreateDirectory(targetDir);
|
||||
}
|
||||
|
||||
var filePath = Path.Combine(targetDir, $"test_image_{i:D5}.jpg");
|
||||
|
||||
// Skip if already exists
|
||||
if (File.Exists(filePath))
|
||||
continue;
|
||||
|
||||
using var bitmap = new Bitmap(width, height);
|
||||
using var graphics = Graphics.FromImage(bitmap);
|
||||
|
||||
// Fill with a random color background
|
||||
var bgColor = Color.FromArgb(random.Next(256), random.Next(256), random.Next(256));
|
||||
graphics.Clear(bgColor);
|
||||
|
||||
// Draw some random shapes to make it more realistic
|
||||
for (int j = 0; j < 20; j++)
|
||||
{
|
||||
var color = Color.FromArgb(random.Next(256), random.Next(256), random.Next(256));
|
||||
var brush = new SolidBrush(color);
|
||||
var x = random.Next(width);
|
||||
var y = random.Next(height);
|
||||
var w = random.Next(200, 800);
|
||||
var h = random.Next(200, 800);
|
||||
graphics.FillEllipse(brush, x, y, w, h);
|
||||
}
|
||||
|
||||
// Add some text
|
||||
using var font = new Font("Arial", 48, FontStyle.Bold);
|
||||
var text = $"Test Image {i}";
|
||||
var textBrush = new SolidBrush(Color.White);
|
||||
graphics.DrawString(text, font, textBrush, new PointF(100, 100));
|
||||
|
||||
// Save as JPEG with standard quality
|
||||
var encoder = GetEncoder(ImageFormat.Jpeg);
|
||||
var encoderParameters = new EncoderParameters(1);
|
||||
encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, 85L);
|
||||
|
||||
bitmap.Save(filePath, encoder, encoderParameters);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleans up generated test images
|
||||
/// </summary>
|
||||
public static void CleanupTestImages(string directory)
|
||||
{
|
||||
if (Directory.Exists(directory))
|
||||
{
|
||||
Directory.Delete(directory, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
private static ImageCodecInfo GetEncoder(ImageFormat format)
|
||||
{
|
||||
var codecs = ImageCodecInfo.GetImageEncoders();
|
||||
foreach (var codec in codecs)
|
||||
{
|
||||
if (codec.FormatID == format.Guid)
|
||||
{
|
||||
return codec;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
182
MaddoShared.Benchmarks/ImageProcessingBenchmarks.cs
Normal file
182
MaddoShared.Benchmarks/ImageProcessingBenchmarks.cs
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Configs;
|
||||
using BenchmarkDotNet.Engines;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using BenchmarkDotNet.Toolchains.InProcess.Emit;
|
||||
using MaddoShared.Benchmarks.Helpers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MaddoShared.Benchmarks;
|
||||
|
||||
/// <summary>
|
||||
/// Benchmarks for image processing with various configurations
|
||||
/// </summary>
|
||||
[MemoryDiagnoser]
|
||||
[Config(typeof(InProcessConfig))]
|
||||
public class ImageProcessingBenchmarks
|
||||
{
|
||||
private string _sourceDirectory;
|
||||
private string _destinationDirectory;
|
||||
private ImageCreationStuff _imageCreationStuff;
|
||||
private PicSettings _picSettings;
|
||||
private ILogger<ImageCreationStuff> _logger;
|
||||
private ILogger<ImageCreatorSharp> _imageCreatorLogger;
|
||||
|
||||
[Params(10, 50, 100)]
|
||||
public int ImageCount { get; set; }
|
||||
|
||||
[Params(1, 2, 4, 8)]
|
||||
public int MaxThreads { get; set; }
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
// Create temp directories
|
||||
var tempBase = Path.Combine(Path.GetTempPath(), "ImageBenchmarks", Guid.NewGuid().ToString());
|
||||
_sourceDirectory = Path.Combine(tempBase, "Source");
|
||||
_destinationDirectory = Path.Combine(tempBase, "Destination");
|
||||
|
||||
Directory.CreateDirectory(_sourceDirectory);
|
||||
Directory.CreateDirectory(_destinationDirectory);
|
||||
|
||||
// Generate test images
|
||||
Console.WriteLine($"Generating {ImageCount} test images...");
|
||||
TestImageGenerator.GenerateTestImages(_sourceDirectory, ImageCount, width: 2000, height: 1500);
|
||||
|
||||
// Setup logging
|
||||
var loggerFactory = LoggerFactory.Create(builder =>
|
||||
{
|
||||
builder.SetMinimumLevel(LogLevel.Warning); // Reduce noise during benchmarks
|
||||
});
|
||||
|
||||
_logger = loggerFactory.CreateLogger<ImageCreationStuff>();
|
||||
_imageCreatorLogger = loggerFactory.CreateLogger<ImageCreatorSharp>();
|
||||
|
||||
// Setup PicSettings with default values
|
||||
_picSettings = new PicSettings
|
||||
{
|
||||
DirectorySorgente = _sourceDirectory,
|
||||
DirectoryDestinazione = _destinationDirectory,
|
||||
DimStandard = 800,
|
||||
DimStandardMiniatura = 200,
|
||||
LarghezzaBig = 1024,
|
||||
AltezzaBig = 768,
|
||||
LarghezzaSmall = 200,
|
||||
AltezzaSmall = 150,
|
||||
CreaMiniature = true,
|
||||
AggiungiScritteMiniature = false,
|
||||
UsaForzaJpg = true,
|
||||
UsaRotazioneAutomatica = true,
|
||||
LogoAggiungi = false,
|
||||
FotoGrandeDimOrigina = false,
|
||||
TestoNome = false,
|
||||
NomeData = false,
|
||||
Suffisso = "_small",
|
||||
Margine = 10,
|
||||
Trasparenza = 100
|
||||
};
|
||||
|
||||
var imageCreatorService = new ImageCreatorSharp(_picSettings, _imageCreatorLogger);
|
||||
_imageCreationStuff = new ImageCreationStuff(_logger, _picSettings, imageCreatorService);
|
||||
}
|
||||
|
||||
[GlobalCleanup]
|
||||
public void Cleanup()
|
||||
{
|
||||
// Clean up temp directories
|
||||
try
|
||||
{
|
||||
var tempBase = Path.GetDirectoryName(_sourceDirectory);
|
||||
if (Directory.Exists(tempBase))
|
||||
{
|
||||
Directory.Delete(tempBase, recursive: true);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Cleanup error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
[IterationSetup]
|
||||
public void IterationSetup()
|
||||
{
|
||||
// Clean destination directory before each iteration
|
||||
if (Directory.Exists(_destinationDirectory))
|
||||
{
|
||||
Directory.Delete(_destinationDirectory, recursive: true);
|
||||
}
|
||||
Directory.CreateDirectory(_destinationDirectory);
|
||||
}
|
||||
|
||||
[Benchmark(Description = "Process images in parallel with chunking")]
|
||||
public async Task ProcessImagesParallelWithChunks()
|
||||
{
|
||||
var options = new ImageCreationStuff.Options
|
||||
{
|
||||
SourcePath = _sourceDirectory,
|
||||
DestinationPath = _destinationDirectory,
|
||||
MaxThreads = MaxThreads,
|
||||
ChunksSize = 10,
|
||||
LinearExecution = false,
|
||||
AggiornaSottodirectory = false,
|
||||
CreaSottocartelle = false,
|
||||
FilePerCartella = 100,
|
||||
SuffissoCartelle = "",
|
||||
CifreContatore = 4,
|
||||
NumerazioneType = NumerazioneType.Progressiva
|
||||
};
|
||||
|
||||
var results = new ConcurrentBag<string>();
|
||||
await _imageCreationStuff.ProcessImagesParallel(options, results, null, CancellationToken.None);
|
||||
}
|
||||
|
||||
[Benchmark(Description = "Process images in parallel without chunking")]
|
||||
public async Task ProcessImagesParallelWithoutChunks()
|
||||
{
|
||||
var options = new ImageCreationStuff.Options
|
||||
{
|
||||
SourcePath = _sourceDirectory,
|
||||
DestinationPath = _destinationDirectory,
|
||||
MaxThreads = MaxThreads,
|
||||
ChunksSize = 0, // No chunking
|
||||
LinearExecution = false,
|
||||
AggiornaSottodirectory = false,
|
||||
CreaSottocartelle = false,
|
||||
FilePerCartella = 100,
|
||||
SuffissoCartelle = "",
|
||||
CifreContatore = 4,
|
||||
NumerazioneType = NumerazioneType.Progressiva
|
||||
};
|
||||
|
||||
var results = new ConcurrentBag<string>();
|
||||
await _imageCreationStuff.ProcessImagesParallel(options, results, null, CancellationToken.None);
|
||||
}
|
||||
|
||||
[Benchmark(Description = "Process images linearly")]
|
||||
public async Task ProcessImagesLinear()
|
||||
{
|
||||
var options = new ImageCreationStuff.Options
|
||||
{
|
||||
SourcePath = _sourceDirectory,
|
||||
DestinationPath = _destinationDirectory,
|
||||
MaxThreads = MaxThreads,
|
||||
ChunksSize = 0,
|
||||
LinearExecution = true,
|
||||
AggiornaSottodirectory = false,
|
||||
CreaSottocartelle = false,
|
||||
FilePerCartella = 100,
|
||||
SuffissoCartelle = "",
|
||||
CifreContatore = 4,
|
||||
NumerazioneType = NumerazioneType.Progressiva
|
||||
};
|
||||
|
||||
var results = new ConcurrentBag<string>();
|
||||
await _imageCreationStuff.ProcessImagesParallel(options, results, null, CancellationToken.None);
|
||||
}
|
||||
}
|
||||
151
MaddoShared.Benchmarks/ImageSizeBenchmarks.cs
Normal file
151
MaddoShared.Benchmarks/ImageSizeBenchmarks.cs
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Configs;
|
||||
using BenchmarkDotNet.Engines;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using BenchmarkDotNet.Toolchains.InProcess.Emit;
|
||||
using MaddoShared.Benchmarks.Helpers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MaddoShared.Benchmarks;
|
||||
|
||||
/// <summary>
|
||||
/// Benchmarks for comparing performance with different image sizes
|
||||
/// </summary>
|
||||
[MemoryDiagnoser]
|
||||
[Config(typeof(InProcessConfig))]
|
||||
public class ImageSizeBenchmarks
|
||||
{
|
||||
private string _sourceDirectory;
|
||||
private string _destinationDirectory;
|
||||
private ImageCreationStuff _imageCreationStuff;
|
||||
private PicSettings _picSettings;
|
||||
|
||||
[Params(50)]
|
||||
public int ImageCount { get; set; }
|
||||
|
||||
public enum ImageSize
|
||||
{
|
||||
Small, // 1280x960
|
||||
Medium, // 2560x1920
|
||||
Large, // 4000x3000
|
||||
ExtraLarge // 6000x4000
|
||||
}
|
||||
|
||||
[ParamsAllValues]
|
||||
public ImageSize Size { get; set; }
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
var tempBase = Path.Combine(Path.GetTempPath(), "SizeBenchmarks", Guid.NewGuid().ToString());
|
||||
_sourceDirectory = Path.Combine(tempBase, "Source");
|
||||
_destinationDirectory = Path.Combine(tempBase, "Destination");
|
||||
|
||||
Directory.CreateDirectory(_sourceDirectory);
|
||||
Directory.CreateDirectory(_destinationDirectory);
|
||||
|
||||
var (width, height) = GetDimensions(Size);
|
||||
Console.WriteLine($"Generating {ImageCount} test images at {width}x{height}...");
|
||||
TestImageGenerator.GenerateTestImages(_sourceDirectory, ImageCount, width, height);
|
||||
|
||||
var loggerFactory = LoggerFactory.Create(builder =>
|
||||
{
|
||||
builder.SetMinimumLevel(LogLevel.Warning);
|
||||
});
|
||||
|
||||
var logger = loggerFactory.CreateLogger<ImageCreationStuff>();
|
||||
var imageCreatorLogger = loggerFactory.CreateLogger<ImageCreatorSharp>();
|
||||
|
||||
_picSettings = new PicSettings
|
||||
{
|
||||
DirectorySorgente = _sourceDirectory,
|
||||
DirectoryDestinazione = _destinationDirectory,
|
||||
DimStandard = 800,
|
||||
DimStandardMiniatura = 200,
|
||||
LarghezzaBig = 1024,
|
||||
AltezzaBig = 768,
|
||||
LarghezzaSmall = 200,
|
||||
AltezzaSmall = 150,
|
||||
CreaMiniature = true,
|
||||
AggiungiScritteMiniature = false,
|
||||
UsaForzaJpg = true,
|
||||
UsaRotazioneAutomatica = true,
|
||||
LogoAggiungi = false,
|
||||
FotoGrandeDimOrigina = false,
|
||||
TestoNome = false,
|
||||
NomeData = false,
|
||||
Suffisso = "_small",
|
||||
Margine = 10,
|
||||
Trasparenza = 100
|
||||
};
|
||||
|
||||
var imageCreatorService = new ImageCreatorSharp(_picSettings, imageCreatorLogger);
|
||||
_imageCreationStuff = new ImageCreationStuff(logger, _picSettings, imageCreatorService);
|
||||
}
|
||||
|
||||
private static (int width, int height) GetDimensions(ImageSize size)
|
||||
{
|
||||
return size switch
|
||||
{
|
||||
ImageSize.Small => (1280, 960),
|
||||
ImageSize.Medium => (2560, 1920),
|
||||
ImageSize.Large => (4000, 3000),
|
||||
ImageSize.ExtraLarge => (6000, 4000),
|
||||
_ => throw new ArgumentException($"Unknown size: {size}")
|
||||
};
|
||||
}
|
||||
|
||||
[GlobalCleanup]
|
||||
public void Cleanup()
|
||||
{
|
||||
try
|
||||
{
|
||||
var tempBase = Path.GetDirectoryName(_sourceDirectory);
|
||||
if (Directory.Exists(tempBase))
|
||||
{
|
||||
Directory.Delete(tempBase, recursive: true);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Cleanup error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
[IterationSetup]
|
||||
public void IterationSetup()
|
||||
{
|
||||
if (Directory.Exists(_destinationDirectory))
|
||||
{
|
||||
Directory.Delete(_destinationDirectory, recursive: true);
|
||||
}
|
||||
Directory.CreateDirectory(_destinationDirectory);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async Task ProcessDifferentImageSizes()
|
||||
{
|
||||
var options = new ImageCreationStuff.Options
|
||||
{
|
||||
SourcePath = _sourceDirectory,
|
||||
DestinationPath = _destinationDirectory,
|
||||
MaxThreads = Environment.ProcessorCount,
|
||||
ChunksSize = 10,
|
||||
LinearExecution = false,
|
||||
AggiornaSottodirectory = false,
|
||||
CreaSottocartelle = false,
|
||||
FilePerCartella = 100,
|
||||
SuffissoCartelle = "",
|
||||
CifreContatore = 4,
|
||||
NumerazioneType = NumerazioneType.Progressiva
|
||||
};
|
||||
|
||||
var results = new ConcurrentBag<string>();
|
||||
await _imageCreationStuff.ProcessImagesParallel(options, results, null, CancellationToken.None);
|
||||
}
|
||||
}
|
||||
21
MaddoShared.Benchmarks/MaddoShared.Benchmarks.csproj
Normal file
21
MaddoShared.Benchmarks/MaddoShared.Benchmarks.csproj
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0-windows</TargetFramework>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<ImportWindowsDesktopTargets>true</ImportWindowsDesktopTargets>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MaddoShared\MaddoShared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
35
MaddoShared.Benchmarks/Program.cs
Normal file
35
MaddoShared.Benchmarks/Program.cs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
using BenchmarkDotNet.Configs;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using BenchmarkDotNet.Running;
|
||||
using BenchmarkDotNet.Toolchains.InProcess.Emit;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace MaddoShared.Benchmarks;
|
||||
|
||||
internal class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
// Check if --job argument is provided
|
||||
bool hasJobArg = args.Any(a => a.Contains("--job"));
|
||||
|
||||
if (hasJobArg)
|
||||
{
|
||||
Console.WriteLine("Note: Overriding --job argument to use InProcess toolchain");
|
||||
Console.WriteLine("This is required to avoid net10.0 vs net10.0-windows compatibility issues.");
|
||||
Console.WriteLine();
|
||||
|
||||
// Remove --job arguments and add our own InProcess config
|
||||
args = args.Where(a => !a.StartsWith("--job") && a != "dry" && a != "short").ToArray();
|
||||
}
|
||||
|
||||
// Create configuration that always uses InProcess toolchain
|
||||
var config = DefaultConfig.Instance
|
||||
.WithOptions(ConfigOptions.DisableOptimizationsValidator)
|
||||
.WithOptions(ConfigOptions.KeepBenchmarkFiles);
|
||||
|
||||
// Run benchmarks - each class has [Config(typeof(InProcessConfig))] which provides InProcess toolchain
|
||||
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, config);
|
||||
}
|
||||
}
|
||||
172
MaddoShared.Benchmarks/StressTestBenchmark.cs
Normal file
172
MaddoShared.Benchmarks/StressTestBenchmark.cs
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Configs;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using BenchmarkDotNet.Toolchains.InProcess.Emit;
|
||||
using MaddoShared.Benchmarks.Helpers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MaddoShared.Benchmarks;
|
||||
|
||||
/// <summary>
|
||||
/// Stress test benchmark for large-scale image processing
|
||||
/// WARNING: This will generate a large number of images and may take significant time and disk space
|
||||
/// </summary>
|
||||
[MemoryDiagnoser]
|
||||
[Config(typeof(InProcessConfig))]
|
||||
public class StressTestBenchmark
|
||||
{
|
||||
private string _sourceDirectory;
|
||||
private string _destinationDirectory;
|
||||
private ImageCreationStuff _imageCreationStuff;
|
||||
private PicSettings _picSettings;
|
||||
|
||||
[Params(500, 1000)]
|
||||
public int ImageCount { get; set; }
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
var tempBase = Path.Combine(Path.GetTempPath(), "StressTestBenchmarks", Guid.NewGuid().ToString());
|
||||
_sourceDirectory = Path.Combine(tempBase, "Source");
|
||||
_destinationDirectory = Path.Combine(tempBase, "Destination");
|
||||
|
||||
Directory.CreateDirectory(_sourceDirectory);
|
||||
Directory.CreateDirectory(_destinationDirectory);
|
||||
|
||||
Console.WriteLine($"[STRESS TEST] Generating {ImageCount} test images...");
|
||||
Console.WriteLine("This may take several minutes depending on your hardware.");
|
||||
|
||||
// Use smaller images for stress test to save space and time
|
||||
TestImageGenerator.GenerateTestImages(_sourceDirectory, ImageCount, width: 1920, height: 1080);
|
||||
|
||||
var loggerFactory = LoggerFactory.Create(builder =>
|
||||
{
|
||||
builder.SetMinimumLevel(LogLevel.Warning);
|
||||
});
|
||||
|
||||
var logger = loggerFactory.CreateLogger<ImageCreationStuff>();
|
||||
var imageCreatorLogger = loggerFactory.CreateLogger<ImageCreatorSharp>();
|
||||
|
||||
_picSettings = new PicSettings
|
||||
{
|
||||
DirectorySorgente = _sourceDirectory,
|
||||
DirectoryDestinazione = _destinationDirectory,
|
||||
DimStandard = 800,
|
||||
DimStandardMiniatura = 200,
|
||||
LarghezzaBig = 1024,
|
||||
AltezzaBig = 768,
|
||||
LarghezzaSmall = 200,
|
||||
AltezzaSmall = 150,
|
||||
CreaMiniature = true,
|
||||
AggiungiScritteMiniature = false,
|
||||
UsaForzaJpg = true,
|
||||
UsaRotazioneAutomatica = true,
|
||||
LogoAggiungi = false,
|
||||
FotoGrandeDimOrigina = false,
|
||||
TestoNome = false,
|
||||
NomeData = false,
|
||||
Suffisso = "_small",
|
||||
Margine = 10,
|
||||
Trasparenza = 100
|
||||
};
|
||||
|
||||
var imageCreatorService = new ImageCreatorSharp(_picSettings, imageCreatorLogger);
|
||||
_imageCreationStuff = new ImageCreationStuff(logger, _picSettings, imageCreatorService);
|
||||
|
||||
Console.WriteLine($"[STRESS TEST] Setup complete. Ready to process {ImageCount} images.");
|
||||
}
|
||||
|
||||
[GlobalCleanup]
|
||||
public void Cleanup()
|
||||
{
|
||||
Console.WriteLine("[STRESS TEST] Cleaning up test data...");
|
||||
try
|
||||
{
|
||||
var tempBase = Path.GetDirectoryName(_sourceDirectory);
|
||||
if (Directory.Exists(tempBase))
|
||||
{
|
||||
Directory.Delete(tempBase, recursive: true);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Cleanup error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
[IterationSetup]
|
||||
public void IterationSetup()
|
||||
{
|
||||
if (Directory.Exists(_destinationDirectory))
|
||||
{
|
||||
Directory.Delete(_destinationDirectory, recursive: true);
|
||||
}
|
||||
Directory.CreateDirectory(_destinationDirectory);
|
||||
GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, blocking: true, compacting: true);
|
||||
}
|
||||
|
||||
[Benchmark(Description = "Stress test with optimal settings")]
|
||||
public async Task StressTestOptimalSettings()
|
||||
{
|
||||
var options = new ImageCreationStuff.Options
|
||||
{
|
||||
SourcePath = _sourceDirectory,
|
||||
DestinationPath = _destinationDirectory,
|
||||
MaxThreads = Environment.ProcessorCount,
|
||||
ChunksSize = 25, // Process in chunks to manage memory
|
||||
LinearExecution = false,
|
||||
AggiornaSottodirectory = false,
|
||||
CreaSottocartelle = false,
|
||||
FilePerCartella = 100,
|
||||
SuffissoCartelle = "",
|
||||
CifreContatore = 4,
|
||||
NumerazioneType = NumerazioneType.Progressiva
|
||||
};
|
||||
|
||||
var results = new ConcurrentBag<string>();
|
||||
var startTime = DateTime.Now;
|
||||
|
||||
await _imageCreationStuff.ProcessImagesParallel(options, results, null, CancellationToken.None);
|
||||
|
||||
var duration = DateTime.Now - startTime;
|
||||
var throughput = ImageCount / duration.TotalSeconds;
|
||||
|
||||
Console.WriteLine($"[STRESS TEST] Processed {results.Count}/{ImageCount} images in {duration.TotalSeconds:F2}s");
|
||||
Console.WriteLine($"[STRESS TEST] Throughput: {throughput:F2} images/second");
|
||||
}
|
||||
|
||||
[Benchmark(Description = "Stress test with aggressive memory management")]
|
||||
public async Task StressTestAggressiveMemoryManagement()
|
||||
{
|
||||
var options = new ImageCreationStuff.Options
|
||||
{
|
||||
SourcePath = _sourceDirectory,
|
||||
DestinationPath = _destinationDirectory,
|
||||
MaxThreads = Environment.ProcessorCount / 2, // Reduce threads to save memory
|
||||
ChunksSize = 10, // Smaller chunks for more frequent GC
|
||||
LinearExecution = false,
|
||||
AggiornaSottodirectory = false,
|
||||
CreaSottocartelle = false,
|
||||
FilePerCartella = 100,
|
||||
SuffissoCartelle = "",
|
||||
CifreContatore = 4,
|
||||
NumerazioneType = NumerazioneType.Progressiva
|
||||
};
|
||||
|
||||
var results = new ConcurrentBag<string>();
|
||||
var startTime = DateTime.Now;
|
||||
|
||||
await _imageCreationStuff.ProcessImagesParallel(options, results, null, CancellationToken.None);
|
||||
|
||||
var duration = DateTime.Now - startTime;
|
||||
var throughput = ImageCount / duration.TotalSeconds;
|
||||
|
||||
Console.WriteLine($"[STRESS TEST] Processed {results.Count}/{ImageCount} images in {duration.TotalSeconds:F2}s");
|
||||
Console.WriteLine($"[STRESS TEST] Throughput: {throughput:F2} images/second");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue