// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using BenchmarkDotNet.Configs; using BenchmarkDotNet.Jobs; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Benchmarks.Codecs { using System; using System.Collections.Generic; using System.Drawing; using System.IO; using System.Linq; using System.Numerics; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Diagnosers; using BenchmarkDotNet.Environments; using SixLabors.ImageSharp.Tests; using CoreImage = SixLabors.ImageSharp.Image; public abstract class MultiImageBenchmarkBase { public class Config : ManualConfig { public Config() { // Uncomment if you want to use any of the diagnoser this.Add(MemoryDiagnoser.Default); } public class ShortClr : Benchmarks.Config { public ShortClr() { this.Add(Job.Default.With(CoreRuntime.Core21).WithLaunchCount(1).WithWarmupCount(1).WithIterationCount(2)); } } } protected Dictionary fileNamesToBytes = new Dictionary(); protected Dictionary> fileNamesToImageSharpImages = new Dictionary>(); protected Dictionary fileNamesToSystemDrawingImages = new Dictionary(); /// /// The values of this enum separate input files into categories. /// public enum InputImageCategory { /// /// Use all images. /// AllImages, /// /// Use small images only. /// SmallImagesOnly, /// /// Use large images only. /// LargeImagesOnly } [Params(InputImageCategory.AllImages, InputImageCategory.SmallImagesOnly, InputImageCategory.LargeImagesOnly)] public virtual InputImageCategory InputCategory { get; set; } protected virtual string BaseFolder => TestEnvironment.InputImagesDirectoryFullPath; protected virtual IEnumerable SearchPatterns => new[] { "*.*" }; /// /// Gets the file names containing these strings are substrings are not processed by the benchmark. /// protected virtual IEnumerable ExcludeSubstringsInFileNames => new[] { "badeof", "BadEof", "CriticalEOF" }; /// /// Gets folders containing files OR files to be processed by the benchmark. /// protected IEnumerable AllFoldersOrFiles => this.InputImageSubfoldersOrFiles.Select(f => Path.Combine(this.BaseFolder, f)); /// /// Gets the large image threshold. /// The images sized above this threshold will be included in. /// protected virtual int LargeImageThresholdInBytes => 100000; protected IEnumerable> EnumeratePairsByBenchmarkSettings( Dictionary input, Predicate checkIfSmall) { switch (this.InputCategory) { case InputImageCategory.AllImages: return input; case InputImageCategory.SmallImagesOnly: return input.Where(kv => checkIfSmall(kv.Value)); case InputImageCategory.LargeImagesOnly: return input.Where(kv => !checkIfSmall(kv.Value)); default: throw new ArgumentOutOfRangeException(); } } protected IEnumerable> FileNames2Bytes => this.EnumeratePairsByBenchmarkSettings( this.fileNamesToBytes, arr => arr.Length < this.LargeImageThresholdInBytes); protected abstract IEnumerable InputImageSubfoldersOrFiles { get; } [GlobalSetup] public virtual void Setup() { if (!Vector.IsHardwareAccelerated) { throw new Exception("Vector.IsHardwareAccelerated == false! Check your build settings!"); } // Console.WriteLine("Vector.IsHardwareAccelerated: " + Vector.IsHardwareAccelerated); this.ReadFilesImpl(); } protected virtual void ReadFilesImpl() { foreach (string path in this.AllFoldersOrFiles) { if (File.Exists(path)) { this.fileNamesToBytes[path] = File.ReadAllBytes(path); continue; } string[] excludeStrings = this.ExcludeSubstringsInFileNames.Select(s => s.ToLower()).ToArray(); string[] allFiles = this.SearchPatterns.SelectMany( f => Directory.EnumerateFiles(path, f, SearchOption.AllDirectories) .Where(fn => !excludeStrings.Any(excludeStr => fn.ToLower().Contains(excludeStr)))).ToArray(); foreach (string fn in allFiles) { this.fileNamesToBytes[fn] = File.ReadAllBytes(fn); } } } /// /// Execute code for each image stream. If the returned object of the operation is it will be disposed. /// /// The operation to execute. If the returned object is <see cref="IDisposable"/> it will be disposed protected void ForEachStream(Func operation) { foreach (KeyValuePair kv in this.FileNames2Bytes) { using (var memoryStream = new MemoryStream(kv.Value)) { try { object obj = operation(memoryStream); (obj as IDisposable)?.Dispose(); } catch (Exception ex) { Console.WriteLine($"Operation on {kv.Key} failed with {ex.Message}"); } } } } public abstract class WithImagesPreloaded : MultiImageBenchmarkBase { protected override void ReadFilesImpl() { base.ReadFilesImpl(); foreach (KeyValuePair kv in this.fileNamesToBytes) { byte[] bytes = kv.Value; string fn = kv.Key; using (var ms1 = new MemoryStream(bytes)) { this.fileNamesToImageSharpImages[fn] = CoreImage.Load(ms1); } this.fileNamesToSystemDrawingImages[fn] = new Bitmap(new MemoryStream(bytes)); } } protected IEnumerable>> FileNames2ImageSharpImages => this.EnumeratePairsByBenchmarkSettings( this.fileNamesToImageSharpImages, img => img.Width * img.Height < this.LargeImageThresholdInPixels); protected IEnumerable> FileNames2SystemDrawingImages => this.EnumeratePairsByBenchmarkSettings( this.fileNamesToSystemDrawingImages, img => img.Width * img.Height < this.LargeImageThresholdInPixels); protected virtual int LargeImageThresholdInPixels => 700000; protected void ForEachImageSharpImage(Func, object> operation) { foreach (KeyValuePair> kv in this.FileNames2ImageSharpImages) { try { object obj = operation(kv.Value); (obj as IDisposable)?.Dispose(); } catch (Exception ex) { Console.WriteLine($"Operation on {kv.Key} failed with {ex.Message}"); } } } protected void ForEachImageSharpImage(Func, MemoryStream, object> operation) { using (var workStream = new MemoryStream()) { this.ForEachImageSharpImage( img => { // ReSharper disable AccessToDisposedClosure object result = operation(img, workStream); workStream.Seek(0, SeekOrigin.Begin); // ReSharper restore AccessToDisposedClosure return result; }); } } protected void ForEachSystemDrawingImage(Func operation) { foreach (KeyValuePair kv in this.FileNames2SystemDrawingImages) { try { object obj = operation(kv.Value); (obj as IDisposable)?.Dispose(); } catch (Exception ex) { Console.WriteLine($"Operation on {kv.Key} failed with {ex.Message}"); } } } protected void ForEachSystemDrawingImage(Func operation) { using (var workStream = new MemoryStream()) { this.ForEachSystemDrawingImage( img => { // ReSharper disable AccessToDisposedClosure object result = operation(img, workStream); workStream.Seek(0, SeekOrigin.Begin); // ReSharper restore AccessToDisposedClosure return result; }); } } } } }