Skip to content

Reuse one stream in codec benchmarks - #3272

Merged
JimBobSquarePants merged 2 commits into
mainfrom
js/perf-codec-benchmarks
Nov 13, 2026
Merged

JimBobSquarePants merged 2 commits into
mainfrom
js/perf-codec-benchmarks

Conversation

@JimBobSquarePants

Copy link
Copy Markdown
Member

Prerequisites

  • I have written a descriptive pull-request title
  • I have verified that there are no overlapping pull-requests open
  • I have verified that I am following the existing coding patterns and practice as demonstrated in the repository. These follow strict Stylecop rules 👮.
  • I have provided test coverage for my change (where applicable)

Description

The codec benchmarks created a new MemoryStream in each benchmark call. The timing then included the growth and copies of that stream, and Gen2 collections for its large arrays. In production, a caller would use a recycled stream. This PR applies the change from the BMP benchmarks in #3271 to the other codec benchmarks.

Each changed benchmark now uses one stream for both libraries:

  • Encode benchmarks write into one MemoryStream that [GlobalSetup] creates. Each call sets Position = 0.
  • Decode benchmarks read from one read-only MemoryStream over the input bytes. Each call sets Position = 0.
  • [GlobalCleanup] disposes the stream.

The results now measure the codec, not the growth of a new stream.

Special cases:

  • EncodeTiff calls SetLength(0) instead of Position = 0. With a rewound stream, the GDI+ TIFF encoder threw ExternalException ("A generic error occurred in GDI+.") for None and Ccitt1D.
  • DecodeJpeg_ImageSpecific gives Skia an SKManagedStream(stream, false). SKCodec.Create(Stream) disposes the stream together with the codec.
  • In DecodeTga, ImageMagick and ImageSharp read the shared stream. Pfim still reads the byte array.
  • EncodeJxl used a new pooled ChunkedMemoryStream in each call. It now uses the shared MemoryStream. I updated Codecs/Jxl/README.md to match.

I ran each changed benchmark and replaced the result table in its comment. IdentifyJpeg, EncodeJxl, EncodeIndexedPng and EncodeGif_CoarsePaletteEncoder get their first table. The old table of DecodeJpegParseStreamOnly was outside the class. The new table is inside the class.

These benchmarks already reused their stream, so I did not change them: DecodeEncodeGif, DecodeJpeg, EncodeJpegComparison, EncodeJpegFeatures, EncodeJpegIcc and EncodeTiffIcc.

This change touches only benchmark code, so it adds no tests.

Changed files in tests/ImageSharp.Benchmarks/Codecs:

  • Exr/DecodeExr.cs
  • Gif/DecodeGif.cs
  • Gif/EncodeGif.cs
  • Heif/DecodeHeif.cs
  • Heif/EncodeHeif.cs
  • Jpeg/DecodeJpegParseStreamOnly.cs
  • Jpeg/DecodeJpeg_ImageSpecific.cs
  • Jpeg/IdentifyJpeg.cs
  • Jxl/EncodeJxl.cs
  • Jxl/README.md
  • Png/DecodePng.cs
  • Png/EncodeIndexedPng.cs
  • Png/EncodePng.cs
  • Tga/DecodeTga.cs
  • Tga/EncodeTga.cs
  • Tiff/DecodeTiff.cs
  • Tiff/DecodeTiffIcc.cs
  • Tiff/EncodeTiff.cs
  • Webp/DecodeWebp.cs
  • Webp/EncodeWebp.cs

Each codec benchmark now reuses one MemoryStream for both libraries.
Each call rewinds the stream, so the results measure the codec and
not the growth of a new stream. EncodeTiff truncates the stream
instead, because the GDI+ TIFF encoder fails on leftover bytes.

Update the result tables from new runs.
@JimBobSquarePants
JimBobSquarePants merged commit 407ce6b into main Nov 13, 2026
12 checks passed
@JimBobSquarePants
JimBobSquarePants deleted the js/perf-codec-benchmarks branch November 13, 2026 01:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant