Browse Source

Faster multibyte reading and fix test

af/merge-core
James Jackson-South 8 years ago
parent
commit
76429a03c6
  1. 150
      src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/DoubleBufferedStreamReader.cs
  2. 53
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/DoubleBufferedStreams.cs
  3. 10
      tests/ImageSharp.Tests/Formats/Jpg/DoubleBufferedStreamReaderTests.cs

150
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/DoubleBufferedStreamReader.cs

@ -20,15 +20,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
/// </summary>
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;
/// <summary>
/// Initializes a new instance of the <see cref="DoubleBufferedStreamReader"/> 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;
}
/// <summary>
/// Gets the length, in bytes, of the stream
/// </summary>
public long Length { get; }
public long Length => this.length;
/// <summary>
/// Gets or sets the current position within the stream
/// </summary>
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
/// </summary>
/// <param name="count">The number of bytes to skip</param>
[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.
/// </returns>
[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);
}
/// <inheritdoc/>
@ -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;
}
}
}

53
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];

10
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);

Loading…
Cancel
Save