镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 24 additions & 10 deletions tests/ImageSharp.Benchmarks/Codecs/Exr/DecodeExr.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@

private byte[] imageBytes;

// Both libraries read from this stream. Each call rewinds it, so the results measure the decoders only.
private MemoryStream inputStream;

private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);

[Params(TestImages.Exr.Benchmark)]

Check warning on line 27 in tests/ImageSharp.Benchmarks/Codecs/Exr/DecodeExr.cs

View workflow job for this annotation

GitHub Actions / Build (macos-26, net10.0, 10.0.x, -x64, false)

Providing a single value to the

Check warning on line 27 in tests/ImageSharp.Benchmarks/Codecs/Exr/DecodeExr.cs

View workflow job for this annotation

GitHub Actions / Build (windows-latest, net10.0, 10.0.x, -x64, false)

Providing a single value to the
public string TestImage { get; set; }

[GlobalSetup]
Expand All @@ -31,27 +34,38 @@
new ExrConfigurationModule().Configure(this.configuration);

this.imageBytes ??= File.ReadAllBytes(this.TestImageFullPath);
this.inputStream ??= new MemoryStream(this.imageBytes, false);
}

/// <summary>
/// Releases the shared input stream.
/// </summary>
[GlobalCleanup]
public void Cleanup()
{
this.inputStream.Dispose();
this.inputStream = null;
}

[Benchmark(Description = "Magick Exr")]
public uint ExrImageMagick()
{
MagickReadSettings settings = new() { Format = MagickFormat.Exr };
using MemoryStream memoryStream = new(this.imageBytes);
using MagickImage image = new(memoryStream, settings);
this.inputStream.Position = 0;
using MagickImage image = new(this.inputStream, settings);
return image.Width;
}

[Benchmark(Description = "ImageSharp Exr")]
public int ExrImageSharp()
{
using MemoryStream memoryStream = new(this.imageBytes);
using Image<Rgba32> image = Image.Load<Rgba32>(memoryStream);
this.inputStream.Position = 0;
using Image<Rgba32> image = Image.Load<Rgba32>(this.inputStream);
return image.Height;
}

/* Results 28.10.2026
BenchmarkDotNet v0.15.8, Windows 11 (10.0.26300.9711)
/* Results 12.11.2026
BenchmarkDotNet v0.15.8, Windows 11 (10.0.26300.9818)
AMD RYZEN AI MAX+ 395 w/ Radeon 8060S 3.00GHz, 1 CPU, 32 logical and 16 physical cores
.NET SDK 11.0.100-preview.7.26381.103
[Host] : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v4
Expand All @@ -60,10 +74,10 @@
Arguments=/p:DebugType=portable IterationCount=3 LaunchCount=1
WarmupCount=3

| Method | TestImage | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated |
| ----------------- | ----------------------------- | ---------: | ---------: | ---------: | --------: | --------: | --------: | ----------: |
| 'Magick Exr' | Exr/Calliphora_benchmark.exr | 19.17 ms | 1.278 ms | 0.070 ms | - | - | - | 5.07 KB |
| 'ImageSharp Exr' | Exr/Calliphora_benchmark.exr | 25.71 ms | 1.120 ms | 0.061 ms | 31.2500 | 31.2500 | 31.2500 | 40.01 KB |
| Method | TestImage | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated |
|----------------- |----------------------------- |---------:|---------:|---------:|--------:|--------:|--------:|----------:|
| 'Magick Exr' | Exr/Calliphora_benchmark.exr | 19.19 ms | 3.300 ms | 0.181 ms | - | - | - | 5.02 KB |
| 'ImageSharp Exr' | Exr/Calliphora_benchmark.exr | 35.02 ms | 0.561 ms | 0.031 ms | 71.4286 | 71.4286 | 71.4286 | 36.58 KB |

Allocated reports managed memory only.
ImageSharp writes Rgba32 output; Magick uses Q16 samples. This comparison does not measure HDR output.
Expand Down
45 changes: 31 additions & 14 deletions tests/ImageSharp.Benchmarks/Codecs/Gif/DecodeGif.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,30 @@
{
private byte[] gifBytes;

// All three libraries read from this stream. Each call rewinds it, so the results measure the decoders only.
private MemoryStream inputStream;

private string TestImageFullPath
=> Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);

[GlobalSetup]
public void ReadImages() => this.gifBytes ??= File.ReadAllBytes(this.TestImageFullPath);
public void ReadImages()
{
this.gifBytes ??= File.ReadAllBytes(this.TestImageFullPath);
this.inputStream ??= new MemoryStream(this.gifBytes, false);
}

/// <summary>
/// Releases the shared input stream.
/// </summary>
[GlobalCleanup]
public void Cleanup()
{
this.inputStream.Dispose();
this.inputStream = null;
}

[Params(TestImages.Gif.Cheers)]

Check warning on line 44 in tests/ImageSharp.Benchmarks/Codecs/Gif/DecodeGif.cs

View workflow job for this annotation

GitHub Actions / Build (macos-26, net10.0, 10.0.x, -x64, false)

Providing a single value to the

Check warning on line 44 in tests/ImageSharp.Benchmarks/Codecs/Gif/DecodeGif.cs

View workflow job for this annotation

GitHub Actions / Build (windows-latest, net10.0, 10.0.x, -x64, false)

Providing a single value to the
public string TestImage { get; set; }

/// <summary>
Expand All @@ -34,8 +51,8 @@
[Benchmark(Baseline = true, Description = "System.Drawing Gif")]
public SDSize GifSystemDrawing()
{
using MemoryStream memoryStream = new(this.gifBytes);
using SDImage image = SDImage.FromStream(memoryStream);
this.inputStream.Position = 0;
using SDImage image = SDImage.FromStream(this.inputStream);

// Select and render every animation frame. Reading Size alone does not request the later frames.
// Reuse one destination bitmap so rendering forces pixel decoding without allocating one bitmap per frame.
Expand All @@ -60,9 +77,9 @@
[Benchmark(Description = "Magick Gif")]
public int GifMagick()
{
using MemoryStream memoryStream = new(this.gifBytes);
this.inputStream.Position = 0;
MagickReadSettings settings = new() { Format = MagickFormat.Gif };
using MagickImageCollection images = new(memoryStream, settings);
using MagickImageCollection images = new(this.inputStream, settings);

// GIF frames can store only changed rectangles. Compose them into full frames, as ImageSharp does.
images.Coalesce();
Expand All @@ -72,13 +89,13 @@
[Benchmark(Description = "ImageSharp Gif")]
public Size GifImageSharp()
{
using MemoryStream memoryStream = new(this.gifBytes);
using Image<Rgba32> image = Image.Load<Rgba32>(memoryStream);
this.inputStream.Position = 0;
using Image<Rgba32> image = Image.Load<Rgba32>(this.inputStream);
return new Size(image.Width, image.Height);
}

/* Results 28.10.2026
BenchmarkDotNet v0.15.8, Windows 11 (10.0.26300.9711)
/* Results 12.11.2026
BenchmarkDotNet v0.15.8, Windows 11 (10.0.26300.9818)
AMD RYZEN AI MAX+ 395 w/ Radeon 8060S 3.00GHz, 1 CPU, 32 logical and 16 physical cores
.NET SDK 11.0.100-preview.7.26381.103
[Host] : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v4
Expand All @@ -87,11 +104,11 @@
Arguments=/p:DebugType=portable IterationCount=3 LaunchCount=1
WarmupCount=3

| Method | TestImage | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio |
| --------------------- | --------------- | ---------: | ---------: | --------: | ------: | --------: | ----------: | ----------: | ----------: | -----------: | ------------: |
| 'System.Drawing Gif' | Gif/cheers.gif | 127.9 ms | 22.28 ms | 1.22 ms | 1.00 | 0.01 | - | - | - | 3011.91 KB | 1.00 |
| 'Magick Gif' | Gif/cheers.gif | 507.8 ms | 37.76 ms | 2.07 ms | 3.97 | 0.04 | - | - | - | 353.77 KB | 0.12 |
| 'ImageSharp Gif' | Gif/cheers.gif | 101.9 ms | 10.78 ms | 0.59 ms | 0.80 | 0.01 | 2400.0000 | 2400.0000 | 2400.0000 | 339.57 KB | 0.11 |
| Method | TestImage | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio |
|--------------------- |--------------- |---------:|---------:|--------:|------:|--------:|----------:|----------:|----------:|-----------:|------------:|
| 'System.Drawing Gif' | Gif/cheers.gif | 126.6 ms | 12.87 ms | 0.71 ms | 1.00 | 0.01 | - | - | - | 3011.85 KB | 1.00 |
| 'Magick Gif' | Gif/cheers.gif | 517.2 ms | 84.38 ms | 4.63 ms | 4.08 | 0.04 | - | - | - | 353.95 KB | 0.12 |
| 'ImageSharp Gif' | Gif/cheers.gif | 101.3 ms | 15.18 ms | 0.83 ms | 0.80 | 0.01 | 2400.0000 | 2400.0000 | 2400.0000 | 339.54 KB | 0.11 |

Allocated reports managed memory only.
All methods decode the 93 frames of cheers.gif. System.Drawing renders into one reusable bitmap; Magick and ImageSharp retain full frames.
Expand Down
54 changes: 41 additions & 13 deletions tests/ImageSharp.Benchmarks/Codecs/Gif/EncodeGif.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ public abstract class EncodeGif
private Image<Rgba32> bmpCore;
private MagickImageCollection magickImage;

// Both libraries write into this stream. Each call rewinds it, so after the first call it has the full capacity,
// and the results measure the encoders instead of the growth of a new stream.
private MemoryStream outputStream;

protected abstract GifEncoder Encoder { get; }

[Params(TestImages.Gif.Leo, TestImages.Gif.Cheers)]
Expand Down Expand Up @@ -44,6 +48,8 @@ public void ReadImages()
{
frame.ColorType = ColorType.TrueColorAlpha;
}

this.outputStream = new MemoryStream();
}
}

Expand All @@ -54,41 +60,43 @@ public void Cleanup()
this.bmpStream = null;
this.bmpCore.Dispose();
this.magickImage.Dispose();
this.outputStream.Dispose();
}

[Benchmark(Description = "ImageSharp Gif")]
public void GifImageSharp()
{
using MemoryStream memoryStream = new();
this.bmpCore.SaveAsGif(memoryStream, this.Encoder);
this.outputStream.Position = 0;
this.bmpCore.SaveAsGif(this.outputStream, this.Encoder);
}

[Benchmark(Baseline = true, Description = "Magick.NET Gif")]
public void GifMagickNet()
{
using MemoryStream ms = new();
this.magickImage.Write(ms, MagickFormat.Gif);
this.outputStream.Position = 0;
this.magickImage.Write(this.outputStream, MagickFormat.Gif);
}
}

public class EncodeGif_DefaultEncoder : EncodeGif
{
protected override GifEncoder Encoder => new();

/* Results 28.10.2026
BenchmarkDotNet v0.15.8, Windows 11 (10.0.26300.9711)
/* Results 12.11.2026
BenchmarkDotNet v0.15.8, Windows 11 (10.0.26300.9818)
AMD RYZEN AI MAX+ 395 w/ Radeon 8060S 3.00GHz, 1 CPU, 32 logical and 16 physical cores
.NET SDK 11.0.100-preview.7.26381.103
[Host] : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v4
DefaultJob : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v4

| Method | TestImage | Mean | Error | StdDev | Ratio |
| ----------------- | --------------- | ----------: | ---------: | ---------: | ------: |
| 'ImageSharp Gif' | Gif/cheers.gif | 156.68 ms | 1.129 ms | 1.056 ms | 1.03 |
| 'Magick.NET Gif' | Gif/cheers.gif | 151.72 ms | 0.407 ms | 0.381 ms | 1.00 |
| | | | | | |
| 'ImageSharp Gif' | Gif/leo.gif | 16.16 ms | 0.067 ms | 0.063 ms | 1.35 |
| 'Magick.NET Gif' | Gif/leo.gif | 12.00 ms | 0.054 ms | 0.048 ms | 1.00 |

| Method | TestImage | Mean | Error | StdDev | Ratio | RatioSD |
|----------------- |--------------- |----------:|---------:|---------:|------:|--------:|
| 'ImageSharp Gif' | Gif/cheers.gif | 155.87 ms | 1.424 ms | 1.332 ms | 1.00 | 0.01 |
| 'Magick.NET Gif' | Gif/cheers.gif | 156.23 ms | 0.911 ms | 0.761 ms | 1.00 | 0.01 |
| | | | | | | |
| 'ImageSharp Gif' | Gif/leo.gif | 17.35 ms | 0.318 ms | 0.402 ms | 1.43 | 0.04 |
| 'Magick.NET Gif' | Gif/leo.gif | 12.15 ms | 0.191 ms | 0.149 ms | 1.00 | 0.02 |

Both encoders receive full animation frames. Magick source palettes are removed before measurement so its writer must quantize the pixels.
The existing default job has no memory diagnoser, so this table does not report allocations.
Expand All @@ -101,4 +109,24 @@ public class EncodeGif_CoarsePaletteEncoder : EncodeGif
{
Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = KnownDitherings.Bayer4x4, ColorMatchingMode = ColorMatchingMode.Coarse })
};

/* Results 12.11.2026
BenchmarkDotNet v0.15.8, Windows 11 (10.0.26300.9818)
AMD RYZEN AI MAX+ 395 w/ Radeon 8060S 3.00GHz, 1 CPU, 32 logical and 16 physical cores
.NET SDK 11.0.100-preview.7.26381.103
[Host] : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v4
DefaultJob : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v4


| Method | TestImage | Mean | Error | StdDev | Ratio |
|----------------- |--------------- |----------:|---------:|---------:|------:|
| 'ImageSharp Gif' | Gif/cheers.gif | 264.28 ms | 1.677 ms | 1.487 ms | 1.74 |
| 'Magick.NET Gif' | Gif/cheers.gif | 151.64 ms | 0.672 ms | 0.629 ms | 1.00 |
| | | | | | |
| 'ImageSharp Gif' | Gif/leo.gif | 25.34 ms | 0.077 ms | 0.072 ms | 2.15 |
| 'Magick.NET Gif' | Gif/leo.gif | 11.77 ms | 0.019 ms | 0.018 ms | 1.00 |

Both encoders receive full animation frames. Magick source palettes are removed before measurement so its writer must quantize the pixels.
The existing default job has no memory diagnoser, so this table does not report allocations.
*/
}
49 changes: 33 additions & 16 deletions tests/ImageSharp.Benchmarks/Codecs/Heif/DecodeHeif.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ public class DecodeHeif
{
private byte[] avifBytes;

// Both libraries read from this stream. Each call rewinds it, so the results measure the decoders only.
private MemoryStream inputStream;

/// <summary>
/// Gets or sets the AVIF test image.
/// </summary>
Expand All @@ -31,7 +34,11 @@ public class DecodeHeif
/// Reads the encoded test image.
/// </summary>
[GlobalSetup]
public void ReadImages() => this.avifBytes ??= File.ReadAllBytes(this.TestImageFullPath);
public void ReadImages()
{
this.avifBytes ??= File.ReadAllBytes(this.TestImageFullPath);
this.inputStream ??= new MemoryStream(this.avifBytes, false);
}

/// <summary>
/// Pins the benchmark process to the first core and reads the encoded test image.
Expand All @@ -44,6 +51,16 @@ public void ReadImagesOnSingleCore()
this.ReadImages();
}

/// <summary>
/// Releases the shared input stream.
/// </summary>
[GlobalCleanup]
public void Cleanup()
{
this.inputStream.Dispose();
this.inputStream = null;
}

/// <summary>
/// Decodes the image with Magick.NET.
/// </summary>
Expand All @@ -52,8 +69,8 @@ public void ReadImagesOnSingleCore()
public uint AvifMagick()
{
MagickReadSettings settings = new() { Format = MagickFormat.Avif };
using MemoryStream memoryStream = new(this.avifBytes);
using MagickImage image = new(memoryStream, settings);
this.inputStream.Position = 0;
using MagickImage image = new(this.inputStream, settings);
return image.Width;
}

Expand All @@ -71,13 +88,13 @@ public uint AvifMagick()
[Benchmark(Description = "ImageSharp Avif")]
public int Avif()
{
using MemoryStream memoryStream = new(this.avifBytes);
using Image<Rgba32> image = Image.Load<Rgba32>(memoryStream);
this.inputStream.Position = 0;
using Image<Rgba32> image = Image.Load<Rgba32>(this.inputStream);
return image.Height;
}

/* Results 28.10.2026
BenchmarkDotNet v0.15.8, Windows 11 (10.0.26300.9711)
/* Results 12.11.2026
BenchmarkDotNet v0.15.8, Windows 11 (10.0.26300.9818)
AMD RYZEN AI MAX+ 395 w/ Radeon 8060S 3.00GHz, 1 CPU, 32 logical and 16 physical cores
.NET SDK 11.0.100-preview.7.26381.103
[Host] : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v4
Expand All @@ -86,15 +103,15 @@ .NET SDK 11.0.100-preview.7.26381.103
Arguments=/p:DebugType=portable IterationCount=3 LaunchCount=1
WarmupCount=3

| Method | TestImage | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio |
| ------------------------- | ----------------------------- | ---------: | ----------: | ---------: | ------: | --------: | --------: | --------: | --------: | ----------: | ------------: |
| 'Magick Avif' | Heif/Irvine_CA.avif | 20.11 ms | 6.352 ms | 0.348 ms | 1.00 | 0.02 | - | - | - | 5.07 KB | 1.00 |
| 'Magick Avif SingleCore' | Heif/Irvine_CA.avif | 19.34 ms | 4.067 ms | 0.223 ms | 0.96 | 0.02 | - | - | - | 5.08 KB | 1.00 |
| 'ImageSharp Avif' | Heif/Irvine_CA.avif | 11.63 ms | 0.404 ms | 0.022 ms | 0.58 | 0.01 | 78.1250 | 62.5000 | 46.8750 | 560.91 KB | 110.54 |
| | | | | | | | | | | | |
| 'Magick Avif' | Heif/libavif-kodim23-8b.avif | 21.41 ms | 3.719 ms | 0.204 ms | 1.00 | 0.01 | - | - | - | 5.07 KB | 1.00 |
| 'Magick Avif SingleCore' | Heif/libavif-kodim23-8b.avif | 21.76 ms | 11.596 ms | 0.636 ms | 1.02 | 0.03 | - | - | - | 5.08 KB | 1.00 |
| 'ImageSharp Avif' | Heif/libavif-kodim23-8b.avif | 10.53 ms | 0.505 ms | 0.028 ms | 0.49 | 0.00 | 78.1250 | 62.5000 | 46.8750 | 541.21 KB | 106.66 |
| Method | TestImage | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio |
|------------------------- |----------------------------- |---------:|----------:|---------:|------:|--------:|--------:|--------:|--------:|----------:|------------:|
| 'Magick Avif' | Heif/Irvine_CA.avif | 21.79 ms | 7.909 ms | 0.434 ms | 1.00 | 0.02 | - | - | - | 5.02 KB | 1.00 |
| 'Magick Avif SingleCore' | Heif/Irvine_CA.avif | 24.12 ms | 57.377 ms | 3.145 ms | 1.11 | 0.13 | - | - | - | 5.03 KB | 1.00 |
| 'ImageSharp Avif' | Heif/Irvine_CA.avif | 13.25 ms | 2.303 ms | 0.126 ms | 0.61 | 0.01 | 78.1250 | 62.5000 | 46.8750 | 560.9 KB | 111.74 |
| | | | | | | | | | | | |
| 'Magick Avif' | Heif/libavif-kodim23-8b.avif | 23.65 ms | 4.813 ms | 0.264 ms | 1.00 | 0.01 | - | - | - | 5.02 KB | 1.00 |
| 'Magick Avif SingleCore' | Heif/libavif-kodim23-8b.avif | 23.43 ms | 2.761 ms | 0.151 ms | 0.99 | 0.01 | - | - | - | 5.02 KB | 1.00 |
| 'ImageSharp Avif' | Heif/libavif-kodim23-8b.avif | 11.59 ms | 10.820 ms | 0.593 ms | 0.49 | 0.02 | 78.1250 | 62.5000 | 46.8750 | 541.18 KB | 107.81 |

Allocated reports managed memory only.
The existing SingleCore method pins only its own Magick process. Other methods retain their existing threading behavior.
Expand Down
Loading
Loading