diff --git a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/DoubleBufferedStreamReader.cs b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/DoubleBufferedStreamReader.cs
index 458ddc4620..90f55bc5db 100644
--- a/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/DoubleBufferedStreamReader.cs
+++ b/src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/DoubleBufferedStreamReader.cs
@@ -20,15 +20,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
///
public const int ChunkLength = 4096;
+ private const int ChunkLengthMinusOne = ChunkLength - 1;
+
+ private const int ChunkLengthPlusOne = ChunkLength + 1;
+
private readonly Stream stream;
private readonly IManagedByteBuffer buffer;
- private readonly byte[] chunk;
+ private readonly byte[] bufferChunk;
private int bytesRead;
- private long position;
+ private int position;
+
+ private int length;
///
/// Initializes a new instance of the class.
@@ -38,30 +44,27 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
public DoubleBufferedStreamReader(MemoryManager memoryManager, Stream stream)
{
this.stream = stream;
- this.Length = stream.Length;
+ this.length = (int)stream.Length;
this.buffer = memoryManager.AllocateCleanManagedByteBuffer(ChunkLength);
- this.chunk = this.buffer.Array;
+ this.bufferChunk = this.buffer.Array;
}
///
/// Gets the length, in bytes, of the stream
///
- public long Length { get; }
+ public long Length => this.length;
///
/// Gets or sets the current position within the stream
///
public long Position
{
- get
- {
- return this.position;
- }
+ get => this.position;
set
{
// Reset everything. It's easier than tracking.
- this.position = value;
+ this.position = (int)value;
this.bytesRead = ChunkLength;
}
}
@@ -74,19 +77,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int ReadByte()
{
- if (this.position >= this.Length)
+ if (this.position >= this.length)
{
return -1;
}
- if (this.position == 0 || this.bytesRead >= ChunkLength)
+ if (this.position == 0 || this.bytesRead > ChunkLengthMinusOne)
{
return this.ReadByteSlow();
}
else
{
this.position++;
- return this.chunk[this.bytesRead++];
+ return this.bufferChunk[this.bytesRead++];
}
}
@@ -94,6 +97,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// Skips the number of bytes in the stream
///
/// The number of bytes to skip
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Skip(int count)
{
this.Position += count;
@@ -118,35 +122,50 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// of bytes requested if that many bytes are not currently available, or zero (0)
/// if the end of the stream has been reached.
///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public int Read(byte[] buffer, int offset, int count)
{
- int n = 0;
- if (buffer.Length <= ChunkLength)
+ if (buffer.Length < ChunkLengthPlusOne)
{
if (this.position == 0 || count + this.bytesRead > ChunkLength)
{
- // Refill our buffer then copy.
- this.stream.Seek(this.position, SeekOrigin.Begin);
- this.stream.Read(this.chunk, 0, ChunkLength);
- this.bytesRead = 0;
+ return this.ReadToChunkSlow(buffer, offset, count);
}
- Buffer.BlockCopy(this.chunk, this.bytesRead, buffer, offset, count);
- this.position += count;
- this.bytesRead += count;
+ int n = this.length - this.position;
+ if (n > count)
+ {
+ n = count;
+ }
- n = Math.Min(count, (int)(this.Length - this.position));
- }
- else
- {
- // Read to target but don't copy to our chunk.
- this.stream.Seek(this.position, SeekOrigin.Begin);
- n = this.stream.Read(buffer, offset, count);
+ if (n < 0)
+ {
+ n = 0;
+ }
- this.Position += count;
+ if (n < 9)
+ {
+ int byteCount = n;
+ int read = this.bytesRead;
+ byte[] chunk = this.bufferChunk;
+
+ while (--byteCount > -1)
+ {
+ buffer[offset + byteCount] = chunk[read + byteCount];
+ }
+ }
+ else
+ {
+ Buffer.BlockCopy(this.bufferChunk, this.bytesRead, buffer, offset, n);
+ }
+
+ this.position += n;
+ this.bytesRead += n;
+
+ return n;
}
- return Math.Max(n, 0);
+ return this.ReadToBufferSlow(buffer, offset, count);
}
///
@@ -158,12 +177,75 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
[MethodImpl(MethodImplOptions.NoInlining)]
private int ReadByteSlow()
{
- this.stream.Seek(this.position, SeekOrigin.Begin);
- this.stream.Read(this.chunk, 0, ChunkLength);
+ if (this.position != this.stream.Position)
+ {
+ this.stream.Seek(this.position, SeekOrigin.Begin);
+ }
+
+ this.stream.Read(this.bufferChunk, 0, ChunkLength);
this.bytesRead = 0;
this.position++;
- return this.chunk[this.bytesRead++];
+ return this.bufferChunk[this.bytesRead++];
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private int ReadToChunkSlow(byte[] buffer, int offset, int count)
+ {
+ // Refill our buffer then copy.
+ if (this.position != this.stream.Position)
+ {
+ this.stream.Seek(this.position, SeekOrigin.Begin);
+ }
+
+ this.stream.Read(this.bufferChunk, 0, ChunkLength);
+ this.bytesRead = 0;
+
+ int n = this.length - this.position;
+ if (n > count)
+ {
+ n = count;
+ }
+
+ if (n < 0)
+ {
+ n = 0;
+ }
+
+ if (n < 9)
+ {
+ int byteCount = n;
+ int read = this.bytesRead;
+ byte[] chunk = this.bufferChunk;
+
+ while (--byteCount > -1)
+ {
+ buffer[offset + byteCount] = chunk[read + byteCount];
+ }
+ }
+ else
+ {
+ Buffer.BlockCopy(this.bufferChunk, this.bytesRead, buffer, offset, n);
+ }
+
+ this.position += n;
+ this.bytesRead += n;
+
+ return n;
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private int ReadToBufferSlow(byte[] buffer, int offset, int count)
+ {
+ // Read to target but don't copy to our chunk.
+ if (this.position != this.stream.Position)
+ {
+ this.stream.Seek(this.position, SeekOrigin.Begin);
+ }
+
+ int n = this.stream.Read(buffer, offset, count);
+ this.Position += n;
+ return n;
}
}
}
\ No newline at end of file
diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DoubleBufferedStreams.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DoubleBufferedStreams.cs
index d178a4970c..1d76d58a51 100644
--- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DoubleBufferedStreams.cs
+++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DoubleBufferedStreams.cs
@@ -12,17 +12,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
public class DoubleBufferedStreams
{
private byte[] buffer = CreateTestBytes();
+ private byte[] chunk1 = new byte[2];
+ private byte[] chunk2 = new byte[2];
private MemoryStream stream1;
private MemoryStream stream2;
- DoubleBufferedStreamReader reader;
+ private MemoryStream stream3;
+ private MemoryStream stream4;
+ DoubleBufferedStreamReader reader1;
+ DoubleBufferedStreamReader reader2;
[GlobalSetup]
public void CreateStreams()
{
this.stream1 = new MemoryStream(this.buffer);
this.stream2 = new MemoryStream(this.buffer);
- this.reader = new DoubleBufferedStreamReader(Configuration.Default.MemoryManager, this.stream2);
+ this.stream3 = new MemoryStream(this.buffer);
+ this.stream4 = new MemoryStream(this.buffer);
+ this.reader1 = new DoubleBufferedStreamReader(Configuration.Default.MemoryManager, this.stream2);
+ this.reader2 = new DoubleBufferedStreamReader(Configuration.Default.MemoryManager, this.stream2);
}
[GlobalCleanup]
@@ -30,11 +38,14 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
this.stream1?.Dispose();
this.stream2?.Dispose();
- this.reader?.Dispose();
+ this.stream3?.Dispose();
+ this.stream4?.Dispose();
+ this.reader1?.Dispose();
+ this.reader2?.Dispose();
}
[Benchmark(Baseline = true)]
- public int StandardStream()
+ public int StandardStreamReadByte()
{
int r = 0;
Stream stream = this.stream1;
@@ -48,10 +59,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
}
[Benchmark]
- public int DoubleBufferedStream()
+ public int StandardStreamRead()
{
int r = 0;
- DoubleBufferedStreamReader reader = this.reader;
+ Stream stream = this.stream1;
+ byte[] b = this.chunk1;
+
+ for (int i = 0; i < stream.Length / 2; i++)
+ {
+ r += stream.Read(b, 0, 2);
+ }
+
+ return r;
+ }
+
+ [Benchmark]
+ public int DoubleBufferedStreamReadByte()
+ {
+ int r = 0;
+ DoubleBufferedStreamReader reader = this.reader1;
for (int i = 0; i < reader.Length; i++)
{
@@ -61,6 +87,21 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
return r;
}
+ [Benchmark]
+ public int DoubleBufferedStreamRead()
+ {
+ int r = 0;
+ DoubleBufferedStreamReader reader = this.reader2;
+ byte[] b = this.chunk2;
+
+ for (int i = 0; i < reader.Length / 2; i++)
+ {
+ r += reader.Read(b, 0, 2);
+ }
+
+ return r;
+ }
+
private static byte[] CreateTestBytes()
{
byte[] buffer = new byte[DoubleBufferedStreamReader.ChunkLength * 3];
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/DoubleBufferedStreamReaderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/DoubleBufferedStreamReaderTests.cs
index 61017ce9b0..bc099bdc5e 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/DoubleBufferedStreamReaderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/DoubleBufferedStreamReaderTests.cs
@@ -90,16 +90,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
for (int i = 0, o = 0; i < expected.Length / 2; i++, o += 2)
{
- if (o + 2 == expected.Length)
- {
- // We've reached the end of the stream
- Assert.Equal(0, reader.Read(buffer, 0, 2));
- }
- else
- {
- Assert.Equal(2, reader.Read(buffer, 0, 2));
- }
+ Assert.Equal(2, reader.Read(buffer, 0, 2));
Assert.Equal(expected[o], buffer[0]);
Assert.Equal(expected[o + 1], buffer[1]);
Assert.Equal(o + 2, reader.Position);