From 399cfc244fea5ed09488e4cdf3abd0b67a2032dd Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 19 Aug 2020 13:40:26 +0100 Subject: [PATCH] Add optimized Read(Span) API --- src/ImageSharp/IO/ChunkedMemoryStream.cs | 24 ++++++---- .../Memory/MemoryOwnerExtensions.cs | 1 + .../IO/ChunkedMemoryStreamTests.cs | 47 ++++++++++++++----- 3 files changed, 53 insertions(+), 19 deletions(-) diff --git a/src/ImageSharp/IO/ChunkedMemoryStream.cs b/src/ImageSharp/IO/ChunkedMemoryStream.cs index d3e6861d2..bd374a3ce 100644 --- a/src/ImageSharp/IO/ChunkedMemoryStream.cs +++ b/src/ImageSharp/IO/ChunkedMemoryStream.cs @@ -236,11 +236,18 @@ namespace SixLabors.ImageSharp.IO Guard.NotNull(buffer, nameof(buffer)); Guard.MustBeGreaterThanOrEqualTo(offset, 0, nameof(offset)); Guard.MustBeGreaterThanOrEqualTo(count, 0, nameof(count)); - if (buffer.Length - offset < count) - { - throw new ArgumentException($"{offset} subtracted from the buffer length is less than {count}"); - } + Guard.IsFalse(buffer.Length - offset < count, nameof(buffer), $"{offset} subtracted from the buffer length is less than {count}"); + + return this.ReadImpl(buffer.AsSpan().Slice(offset, count)); + } + +#if SUPPORTS_SPAN_STREAM + /// + public override int Read(Span buffer) => this.ReadImpl(buffer); +#endif + private int ReadImpl(Span buffer) + { this.EnsureNotDisposed(); if (this.readChunk is null) @@ -254,7 +261,7 @@ namespace SixLabors.ImageSharp.IO this.readOffset = 0; } - byte[] chunkBuffer = this.readChunk.Buffer.Array; + Span chunkBuffer = this.readChunk.Buffer.GetSpan(); int chunkSize = this.readChunk.Length; if (this.readChunk.Next is null) { @@ -262,7 +269,8 @@ namespace SixLabors.ImageSharp.IO } int bytesRead = 0; - + int offset = 0; + int count = buffer.Length; while (count > 0) { if (this.readOffset == chunkSize) @@ -275,7 +283,7 @@ namespace SixLabors.ImageSharp.IO this.readChunk = this.readChunk.Next; this.readOffset = 0; - chunkBuffer = this.readChunk.Buffer.Array; + chunkBuffer = this.readChunk.Buffer.GetSpan(); chunkSize = this.readChunk.Length; if (this.readChunk.Next is null) { @@ -284,7 +292,7 @@ namespace SixLabors.ImageSharp.IO } int readCount = Math.Min(count, chunkSize - this.readOffset); - Buffer.BlockCopy(chunkBuffer, this.readOffset, buffer, offset, readCount); + chunkBuffer.Slice(this.readOffset, count).CopyTo(buffer.Slice(offset)); offset += readCount; count -= readCount; this.readOffset += readCount; diff --git a/src/ImageSharp/Memory/MemoryOwnerExtensions.cs b/src/ImageSharp/Memory/MemoryOwnerExtensions.cs index 98fd40e65..aa475a80f 100644 --- a/src/ImageSharp/Memory/MemoryOwnerExtensions.cs +++ b/src/ImageSharp/Memory/MemoryOwnerExtensions.cs @@ -13,6 +13,7 @@ namespace SixLabors.ImageSharp.Memory /// internal static class MemoryOwnerExtensions { + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Span GetSpan(this IMemoryOwner buffer) => buffer.Memory.Span; diff --git a/tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs b/tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs index 46afc7e50..28f1d336d 100644 --- a/tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs +++ b/tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs @@ -49,8 +49,8 @@ namespace SixLabors.ImageSharp.Tests.IO Assert.Throws(() => ms2.Read(null, 0, 0)); Assert.Throws(() => ms2.Read(new byte[] { 1 }, -1, 0)); Assert.Throws(() => ms2.Read(new byte[] { 1 }, 0, -1)); - Assert.Throws(null, () => ms2.Read(new byte[] { 1 }, 2, 0)); - Assert.Throws(null, () => ms2.Read(new byte[] { 1 }, 0, 2)); + Assert.Throws(() => ms2.Read(new byte[] { 1 }, 2, 0)); + Assert.Throws(() => ms2.Read(new byte[] { 1 }, 0, 2)); ms2.Dispose(); @@ -58,10 +58,11 @@ namespace SixLabors.ImageSharp.Tests.IO } [Theory] - [InlineData(1024)] - [InlineData(1024 * 4)] - [InlineData(1024 * 6)] - [InlineData(1024 * 8)] + [InlineData(ChunkedMemoryStream.DefaultBufferLength)] + [InlineData((int)(ChunkedMemoryStream.DefaultBufferLength * 1.5))] + [InlineData(ChunkedMemoryStream.DefaultBufferLength * 4)] + [InlineData((int)(ChunkedMemoryStream.DefaultBufferLength * 5.5))] + [InlineData(ChunkedMemoryStream.DefaultBufferLength * 8)] public void MemoryStream_ReadByteTest(int length) { using MemoryStream ms = this.CreateTestStream(length); @@ -78,10 +79,11 @@ namespace SixLabors.ImageSharp.Tests.IO } [Theory] - [InlineData(1024)] - [InlineData(1024 * 4)] - [InlineData(1024 * 6)] - [InlineData(1024 * 8)] + [InlineData(ChunkedMemoryStream.DefaultBufferLength)] + [InlineData((int)(ChunkedMemoryStream.DefaultBufferLength * 1.5))] + [InlineData(ChunkedMemoryStream.DefaultBufferLength * 4)] + [InlineData((int)(ChunkedMemoryStream.DefaultBufferLength * 5.5))] + [InlineData(ChunkedMemoryStream.DefaultBufferLength * 8)] public void MemoryStream_ReadByteBufferTest(int length) { using MemoryStream ms = this.CreateTestStream(length); @@ -99,6 +101,29 @@ namespace SixLabors.ImageSharp.Tests.IO } } + [Theory] + [InlineData(ChunkedMemoryStream.DefaultBufferLength)] + [InlineData((int)(ChunkedMemoryStream.DefaultBufferLength * 1.5))] + [InlineData(ChunkedMemoryStream.DefaultBufferLength * 4)] + [InlineData((int)(ChunkedMemoryStream.DefaultBufferLength * 5.5))] + [InlineData(ChunkedMemoryStream.DefaultBufferLength * 8)] + public void MemoryStream_ReadByteBufferSpanTest(int length) + { + using MemoryStream ms = this.CreateTestStream(length); + using var cms = new ChunkedMemoryStream(this.allocator); + + ms.CopyTo(cms); + cms.Position = 0; + var expected = ms.ToArray(); + Span buffer = new byte[2]; + for (int i = 0; i < expected.Length; i += 2) + { + cms.Read(buffer); + Assert.Equal(expected[i], buffer[0]); + Assert.Equal(expected[i + 1], buffer[1]); + } + } + [Fact] public void MemoryStream_WriteToTests() { @@ -107,7 +132,7 @@ namespace SixLabors.ImageSharp.Tests.IO byte[] bytArrRet; byte[] bytArr = new byte[] { byte.MinValue, byte.MaxValue, 1, 2, 3, 4, 5, 6, 128, 250 }; - // [] Write to FileStream, check the filestream + // [] Write to memoryStream, check the memoryStream ms2.Write(bytArr, 0, bytArr.Length); using var readonlyStream = new ChunkedMemoryStream(this.allocator);