From 9eb23024022e82046ba30926ad245e111029fefd Mon Sep 17 00:00:00 2001 From: Drew Noakes Date: Tue, 6 Feb 2024 22:52:48 +1100 Subject: [PATCH] Pool buffers in IndexedCapturingReader Looking at traces over the regression suite shows ~2% CPU spent allocating 2kB buffers for the `IndexedCapturingReader`. We can pull these buffers from the array pool, and return them when we're done. When processing multiple items, reusing these arrays will improve overall performance. --- MetadataExtractor/Formats/Png/PngMetadataReader.cs | 3 ++- .../Formats/Tiff/TiffMetadataReader.cs | 4 +++- MetadataExtractor/IO/IndexedCapturingReader.cs | 14 ++++++++++++-- .../PublicAPI/net462/PublicAPI.Unshipped.txt | 1 + .../PublicAPI/net8.0/PublicAPI.Unshipped.txt | 1 + .../netstandard1.3/PublicAPI.Unshipped.txt | 1 + .../netstandard2.1/PublicAPI.Unshipped.txt | 1 + 7 files changed, 21 insertions(+), 4 deletions(-) diff --git a/MetadataExtractor/Formats/Png/PngMetadataReader.cs b/MetadataExtractor/Formats/Png/PngMetadataReader.cs index 1a967806d..ad4ecb46c 100644 --- a/MetadataExtractor/Formats/Png/PngMetadataReader.cs +++ b/MetadataExtractor/Formats/Png/PngMetadataReader.cs @@ -170,7 +170,8 @@ private static IEnumerable ProcessChunk(PngChunk chunk) try { using var inflaterStream = new DeflateStream(new MemoryStream(compressedProfile), CompressionMode.Decompress); - iccDirectory = new IccReader().Extract(new IndexedCapturingReader(inflaterStream)); + using var iccReader = new IndexedCapturingReader(inflaterStream); + iccDirectory = new IccReader().Extract(iccReader); iccDirectory.Parent = directory; } catch (Exception e) diff --git a/MetadataExtractor/Formats/Tiff/TiffMetadataReader.cs b/MetadataExtractor/Formats/Tiff/TiffMetadataReader.cs index e714fe2bf..f5bc295c6 100644 --- a/MetadataExtractor/Formats/Tiff/TiffMetadataReader.cs +++ b/MetadataExtractor/Formats/Tiff/TiffMetadataReader.cs @@ -41,7 +41,9 @@ public static IReadOnlyList ReadMetadata(Stream stream) var directories = new List(); var handler = new ExifTiffHandler(directories, exifStartOffset: 0); - TiffReader.ProcessTiff(new IndexedCapturingReader(stream), handler); + + using var reader = new IndexedCapturingReader(stream); + TiffReader.ProcessTiff(reader, handler); return directories; } diff --git a/MetadataExtractor/IO/IndexedCapturingReader.cs b/MetadataExtractor/IO/IndexedCapturingReader.cs index a49a8c6c6..029e46226 100644 --- a/MetadataExtractor/IO/IndexedCapturingReader.cs +++ b/MetadataExtractor/IO/IndexedCapturingReader.cs @@ -1,9 +1,11 @@ // Copyright (c) Drew Noakes and contributors. All Rights Reserved. Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. +using System.Buffers; + namespace MetadataExtractor.IO { /// Drew Noakes https://drewnoakes.com - public sealed class IndexedCapturingReader : IndexedReader + public sealed class IndexedCapturingReader : IndexedReader, IDisposable { private const int DefaultChunkLength = 2 * 1024; @@ -99,7 +101,7 @@ private bool IsValidIndex(int index, int bytesRequested) { Debug.Assert(!_isStreamFinished); - var chunk = new byte[_chunkLength]; + var chunk = ArrayPool.Shared.Rent(_chunkLength); var totalBytesRead = 0; while (!_isStreamFinished && totalBytesRead != _chunkLength) { @@ -129,6 +131,14 @@ private bool IsValidIndex(int index, int bytesRequested) return true; } + public void Dispose() + { + foreach (var chunk in _chunks) + { + ArrayPool.Shared.Return(chunk); + } + } + public override int ToUnshiftedOffset(int localOffset) => localOffset; private void GetPosition(int index, out int chunkIndex, out int innerIndex) diff --git a/MetadataExtractor/PublicAPI/net462/PublicAPI.Unshipped.txt b/MetadataExtractor/PublicAPI/net462/PublicAPI.Unshipped.txt index 5ea5c0461..dbb92d0cb 100644 --- a/MetadataExtractor/PublicAPI/net462/PublicAPI.Unshipped.txt +++ b/MetadataExtractor/PublicAPI/net462/PublicAPI.Unshipped.txt @@ -111,6 +111,7 @@ MetadataExtractor.Formats.Png.PngDirectory.GetPngChunkType() -> MetadataExtracto MetadataExtractor.Formats.Png.PngDirectory.PngDirectory(MetadataExtractor.Formats.Png.PngChunkType pngChunkType) -> void MetadataExtractor.GeoLocation.Equals(MetadataExtractor.GeoLocation other) -> bool MetadataExtractor.GeoLocation.GeoLocation() -> void +MetadataExtractor.IO.IndexedCapturingReader.Dispose() -> void MetadataExtractor.IO.IndexedReader.GetByte(int index) -> byte MetadataExtractor.IO.IndexedReader.GetBytes(int index, int count) -> byte[]! MetadataExtractor.Util.FileType.Avif = 28 -> MetadataExtractor.Util.FileType diff --git a/MetadataExtractor/PublicAPI/net8.0/PublicAPI.Unshipped.txt b/MetadataExtractor/PublicAPI/net8.0/PublicAPI.Unshipped.txt index 8b12216d4..2cc02a266 100644 --- a/MetadataExtractor/PublicAPI/net8.0/PublicAPI.Unshipped.txt +++ b/MetadataExtractor/PublicAPI/net8.0/PublicAPI.Unshipped.txt @@ -110,6 +110,7 @@ MetadataExtractor.Formats.Png.PngDirectory.GetPngChunkType() -> MetadataExtracto MetadataExtractor.Formats.Png.PngDirectory.PngDirectory(MetadataExtractor.Formats.Png.PngChunkType pngChunkType) -> void MetadataExtractor.GeoLocation.Equals(MetadataExtractor.GeoLocation other) -> bool MetadataExtractor.GeoLocation.GeoLocation() -> void +MetadataExtractor.IO.IndexedCapturingReader.Dispose() -> void MetadataExtractor.IO.IndexedReader.GetByte(int index) -> byte MetadataExtractor.IO.IndexedReader.GetBytes(int index, int count) -> byte[]! MetadataExtractor.Util.FileType.Avif = 28 -> MetadataExtractor.Util.FileType diff --git a/MetadataExtractor/PublicAPI/netstandard1.3/PublicAPI.Unshipped.txt b/MetadataExtractor/PublicAPI/netstandard1.3/PublicAPI.Unshipped.txt index 5ea5c0461..dbb92d0cb 100644 --- a/MetadataExtractor/PublicAPI/netstandard1.3/PublicAPI.Unshipped.txt +++ b/MetadataExtractor/PublicAPI/netstandard1.3/PublicAPI.Unshipped.txt @@ -111,6 +111,7 @@ MetadataExtractor.Formats.Png.PngDirectory.GetPngChunkType() -> MetadataExtracto MetadataExtractor.Formats.Png.PngDirectory.PngDirectory(MetadataExtractor.Formats.Png.PngChunkType pngChunkType) -> void MetadataExtractor.GeoLocation.Equals(MetadataExtractor.GeoLocation other) -> bool MetadataExtractor.GeoLocation.GeoLocation() -> void +MetadataExtractor.IO.IndexedCapturingReader.Dispose() -> void MetadataExtractor.IO.IndexedReader.GetByte(int index) -> byte MetadataExtractor.IO.IndexedReader.GetBytes(int index, int count) -> byte[]! MetadataExtractor.Util.FileType.Avif = 28 -> MetadataExtractor.Util.FileType diff --git a/MetadataExtractor/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt b/MetadataExtractor/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt index 8b12216d4..2cc02a266 100644 --- a/MetadataExtractor/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt +++ b/MetadataExtractor/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt @@ -110,6 +110,7 @@ MetadataExtractor.Formats.Png.PngDirectory.GetPngChunkType() -> MetadataExtracto MetadataExtractor.Formats.Png.PngDirectory.PngDirectory(MetadataExtractor.Formats.Png.PngChunkType pngChunkType) -> void MetadataExtractor.GeoLocation.Equals(MetadataExtractor.GeoLocation other) -> bool MetadataExtractor.GeoLocation.GeoLocation() -> void +MetadataExtractor.IO.IndexedCapturingReader.Dispose() -> void MetadataExtractor.IO.IndexedReader.GetByte(int index) -> byte MetadataExtractor.IO.IndexedReader.GetBytes(int index, int count) -> byte[]! MetadataExtractor.Util.FileType.Avif = 28 -> MetadataExtractor.Util.FileType