Browse Source

Cleanup and refactor

pull/1574/head
James Jackson-South 6 years ago
parent
commit
32f8cf0f14
  1. 20
      src/ImageSharp/IO/BufferedReadStream.cs
  2. 255
      src/ImageSharp/IO/DoubleBufferedStreamReader.cs
  3. 4
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs
  4. 132
      tests/ImageSharp.Benchmarks/General/IO/BufferedReadStreamWrapper.cs
  5. 81
      tests/ImageSharp.Benchmarks/General/IO/BufferedStreams.cs
  6. 1
      tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj
  7. 16
      tests/ImageSharp.Tests/IO/BufferedReadStreamTests.cs
  8. 176
      tests/ImageSharp.Tests/IO/DoubleBufferedStreamReaderTests.cs

20
src/ImageSharp/IO/BufferedReadStream.cs

@ -66,20 +66,16 @@ namespace SixLabors.ImageSharp.IO
this.readBufferIndex = BufferLength;
}
/// <summary>
/// Gets the length, in bytes, of the stream.
/// </summary>
/// <inheritdoc/>
public override long Length { get; }
/// <summary>
/// Gets or sets the current position within the stream.
/// </summary>
/// <inheritdoc/>
public override long Position
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.readerPosition;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.NoInlining)]
set
{
// Only reset readBufferIndex if we are out of bounds of our working buffer
@ -185,12 +181,16 @@ namespace SixLabors.ImageSharp.IO
}
/// <inheritdoc/>
/// <exception cref="NotSupportedException">This operation is not supported in <see cref="BufferedReadStream"/>.</exception>
/// <exception cref="NotSupportedException">
/// This operation is not supported in <see cref="BufferedReadStream"/>.
/// </exception>
public override void SetLength(long value)
=> throw new NotSupportedException();
/// <inheritdoc/>
/// <exception cref="NotSupportedException">This operation is not supported in <see cref="BufferedReadStream"/>.</exception>
/// <exception cref="NotSupportedException">
/// This operation is not supported in <see cref="BufferedReadStream"/>.
/// </exception>
public override void Write(byte[] buffer, int offset, int count)
=> throw new NotSupportedException();
@ -239,7 +239,7 @@ namespace SixLabors.ImageSharp.IO
return n;
}
[MethodImpl(MethodImplOptions.NoInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int ReadToBufferViaCopySlow(byte[] buffer, int offset, int count)
{
// Refill our buffer then copy.

255
src/ImageSharp/IO/DoubleBufferedStreamReader.cs

@ -1,255 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.IO;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.IO
{
/// <summary>
/// A stream reader that add a secondary level buffer in addition to native stream buffered reading
/// to reduce the overhead of small incremental reads.
/// </summary>
internal sealed unsafe class DoubleBufferedStreamReader : IDisposable
{
/// <summary>
/// The length, in bytes, of the buffering chunk.
/// </summary>
public const int ChunkLength = 8192;
private const int MaxChunkIndex = ChunkLength - 1;
private readonly Stream stream;
private readonly IManagedByteBuffer managedBuffer;
private MemoryHandle handle;
private readonly byte* pinnedChunk;
private readonly byte[] bufferChunk;
private readonly int length;
private int chunkIndex;
private int position;
/// <summary>
/// Initializes a new instance of the <see cref="DoubleBufferedStreamReader"/> class.
/// </summary>
/// <param name="memoryAllocator">The <see cref="MemoryAllocator"/> to use for buffer allocations.</param>
/// <param name="stream">The input stream.</param>
public DoubleBufferedStreamReader(MemoryAllocator memoryAllocator, Stream stream)
{
this.stream = stream;
this.Position = (int)stream.Position;
this.length = (int)stream.Length;
this.managedBuffer = memoryAllocator.AllocateManagedByteBuffer(ChunkLength);
this.bufferChunk = this.managedBuffer.Array;
this.handle = this.managedBuffer.Memory.Pin();
this.pinnedChunk = (byte*)this.handle.Pointer;
this.chunkIndex = ChunkLength;
}
/// <summary>
/// Gets the length, in bytes, of the stream.
/// </summary>
public long Length => this.length;
/// <summary>
/// Gets or sets the current position within the stream.
/// </summary>
public long Position
{
get => this.position;
set
{
// Only reset chunkIndex if we are out of bounds of our working chunk
// otherwise we should simply move the value by the diff.
int v = (int)value;
if (this.IsInChunk(v, out int index))
{
this.chunkIndex = index;
this.position = v;
}
else
{
this.position = v;
this.stream.Seek(value, SeekOrigin.Begin);
this.chunkIndex = ChunkLength;
}
}
}
/// <summary>
/// Reads a byte from the stream and advances the position within the stream by one
/// byte, or returns -1 if at the end of the stream.
/// </summary>
/// <returns>The unsigned byte cast to an <see cref="int"/>, or -1 if at the end of the stream.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public int ReadByte()
{
if (this.position >= this.length)
{
return -1;
}
if (this.chunkIndex > MaxChunkIndex)
{
this.FillChunk();
}
this.position++;
return this.pinnedChunk[this.chunkIndex++];
}
/// <summary>
/// Skips the number of bytes in the stream
/// </summary>
/// <param name="count">The number of bytes to skip.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public void Skip(int count) => this.Position += count;
/// <summary>
/// Reads a sequence of bytes from the current stream and advances the position within the stream
/// by the number of bytes read.
/// </summary>
/// <param name="buffer">
/// An array of bytes. When this method returns, the buffer contains the specified
/// byte array with the values between offset and (offset + count - 1) replaced by
/// the bytes read from the current source.
/// </param>
/// <param name="offset">
/// The zero-based byte offset in buffer at which to begin storing the data read
/// from the current stream.
/// </param>
/// <param name="count">The maximum number of bytes to be read from the current stream.</param>
/// <returns>
/// The total number of bytes read into the buffer. This can be less than the number
/// 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(InliningOptions.ShortMethod)]
public int Read(byte[] buffer, int offset, int count)
{
if (count > ChunkLength)
{
return this.ReadToBufferSlow(buffer, offset, count);
}
if (count + this.chunkIndex > ChunkLength)
{
return this.ReadToChunkSlow(buffer, offset, count);
}
int n = this.GetCopyCount(count);
this.CopyBytes(buffer, offset, n);
this.position += n;
this.chunkIndex += n;
return n;
}
/// <inheritdoc/>
public void Dispose()
{
this.handle.Dispose();
this.managedBuffer?.Dispose();
}
[MethodImpl(InliningOptions.ShortMethod)]
private int GetPositionDifference(int p) => p - this.position;
[MethodImpl(InliningOptions.ShortMethod)]
private bool IsInChunk(int p, out int index)
{
index = this.GetPositionDifference(p) + this.chunkIndex;
return index > -1 && index < ChunkLength;
}
[MethodImpl(InliningOptions.ColdPath)]
private void FillChunk()
{
if (this.position != this.stream.Position)
{
this.stream.Seek(this.position, SeekOrigin.Begin);
}
this.stream.Read(this.bufferChunk, 0, ChunkLength);
this.chunkIndex = 0;
}
[MethodImpl(InliningOptions.ColdPath)]
private int ReadToChunkSlow(byte[] buffer, int offset, int count)
{
// Refill our buffer then copy.
this.FillChunk();
int n = this.GetCopyCount(count);
this.CopyBytes(buffer, offset, n);
this.position += n;
this.chunkIndex += n;
return n;
}
[MethodImpl(InliningOptions.ColdPath)]
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;
}
[MethodImpl(InliningOptions.ShortMethod)]
private int GetCopyCount(int count)
{
int n = this.length - this.position;
if (n > count)
{
n = count;
}
if (n < 0)
{
n = 0;
}
return n;
}
[MethodImpl(InliningOptions.ShortMethod)]
private void CopyBytes(byte[] buffer, int offset, int count)
{
if (count < 9)
{
int byteCount = count;
int read = this.chunkIndex;
byte* pinned = this.pinnedChunk;
while (--byteCount > -1)
{
buffer[offset + byteCount] = pinned[read + byteCount];
}
}
else
{
Buffer.BlockCopy(this.bufferChunk, this.chunkIndex, buffer, offset, count);
}
}
}
}

4
tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs

@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
public ShortClr()
{
// Job.Default.With(ClrRuntime.Net472).WithLaunchCount(1).WithWarmupCount(2).WithIterationCount(3),
this.Add(Job.Default.With(CoreRuntime.Core21).WithLaunchCount(1).WithWarmupCount(2).WithIterationCount(3));
this.Add(Job.Default.With(CoreRuntime.Core31).WithLaunchCount(1).WithWarmupCount(2).WithIterationCount(3));
}
}
}
@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);
#pragma warning disable SA1115
#pragma warning disable SA1115
[Params(
TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr,
TestImages.Jpeg.BenchmarkSuite.BadRstProgressive518_Large444YCbCr,

132
src/ImageSharp/IO/BufferedReadStream2.cs → tests/ImageSharp.Benchmarks/General/IO/BufferedReadStreamWrapper.cs

@ -6,13 +6,13 @@ using System.Buffers;
using System.IO;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.IO
namespace SixLabors.ImageSharp.Benchmarks.IO
{
/// <summary>
/// A readonly stream that add a secondary level buffer in addition to native stream
/// A readonly stream wrapper that add a secondary level buffer in addition to native stream
/// buffered reading to reduce the overhead of small incremental reads.
/// </summary>
internal sealed unsafe class BufferedReadStream2 : IDisposable
internal sealed unsafe class BufferedReadStreamWrapper : IDisposable
{
/// <summary>
/// The length, in bytes, of the underlying buffer.
@ -29,19 +29,19 @@ namespace SixLabors.ImageSharp.IO
private readonly byte* pinnedReadBuffer;
// Index within our buffer, not reader position.
private int readBufferIndex;
private readonly int length;
private int position;
// Matches what the stream position would be without buffering
private long readerPosition;
private bool isDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="BufferedReadStream2"/> class.
/// Initializes a new instance of the <see cref="BufferedReadStreamWrapper"/> class.
/// </summary>
/// <param name="stream">The input stream.</param>
public BufferedReadStream2(Stream stream)
public BufferedReadStreamWrapper(Stream stream)
{
Guard.IsTrue(stream.CanRead, nameof(stream), "Stream must be readable.");
Guard.IsTrue(stream.CanSeek, nameof(stream), "Stream must be seekable.");
@ -56,7 +56,7 @@ namespace SixLabors.ImageSharp.IO
this.stream = stream;
this.Position = (int)stream.Position;
this.length = (int)stream.Length;
this.Length = stream.Length;
this.readBuffer = ArrayPool<byte>.Shared.Rent(BufferLength);
this.readBufferHandle = new Memory<byte>(this.readBuffer).Pin();
@ -69,113 +69,109 @@ namespace SixLabors.ImageSharp.IO
/// <summary>
/// Gets the length, in bytes, of the stream.
/// </summary>
public long Length => this.length;
public long Length { get; }
/// <summary>
/// Gets or sets the current position within the stream.
/// </summary>
public long Position
{
get => this.position;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.readerPosition;
[MethodImpl(MethodImplOptions.NoInlining)]
set
{
// Only reset readIndex if we are out of bounds of our working buffer
// Only reset readBufferIndex if we are out of bounds of our working buffer
// otherwise we should simply move the value by the diff.
int v = (int)value;
if (this.IsInReadBuffer(v, out int index))
if (this.IsInReadBuffer(value, out long index))
{
this.readBufferIndex = index;
this.position = v;
this.readBufferIndex = (int)index;
this.readerPosition = value;
}
else
{
this.position = v;
// Base stream seek will throw for us if invalid.
this.stream.Seek(value, SeekOrigin.Begin);
this.readerPosition = value;
this.readBufferIndex = BufferLength;
}
}
}
public bool CanRead { get; } = true;
public bool CanSeek { get; } = true;
public bool CanWrite { get; } = false;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int ReadByte()
{
if (this.position >= this.length)
if (this.readerPosition >= this.Length)
{
return -1;
}
// Our buffer has been read.
// We need to refill and start again.
if (this.readBufferIndex > MaxBufferIndex)
{
this.FillReadBuffer();
}
this.position++;
this.readerPosition++;
return this.pinnedReadBuffer[this.readBufferIndex++];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int Read(byte[] buffer, int offset, int count)
{
// Too big for our buffer. Read directly from the stream.
if (count > BufferLength)
{
return this.ReadToBufferDirectSlow(buffer, offset, count);
}
// Too big for remaining buffer but less than entire buffer length
// Copy to buffer then read from there.
if (count + this.readBufferIndex > BufferLength)
{
return this.ReadToBufferViaCopySlow(buffer, offset, count);
}
// return this.ReadToBufferViaCopyFast(buffer, offset, count);
int n = this.GetCopyCount(count);
this.CopyBytes(buffer, offset, n);
this.position += n;
this.readBufferIndex += n;
return n;
return this.ReadToBufferViaCopyFast(buffer, offset, count);
}
public void Flush()
{
// Reset the stream position.
if (this.position != this.stream.Position)
// Reset the stream position to match reader position.
if (this.readerPosition != this.stream.Position)
{
this.stream.Seek(this.position, SeekOrigin.Begin);
this.position = (int)this.stream.Position;
this.stream.Seek(this.readerPosition, SeekOrigin.Begin);
this.readerPosition = (int)this.stream.Position;
}
// Reset to trigger full read on next attempt.
this.readBufferIndex = BufferLength;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public long Seek(long offset, SeekOrigin origin)
{
if (origin == SeekOrigin.Begin)
switch (origin)
{
this.Position = offset;
}
else
{
this.Position += offset;
}
case SeekOrigin.Begin:
this.Position = offset;
break;
return this.position;
}
case SeekOrigin.Current:
this.Position += offset;
break;
public void SetLength(long value)
=> throw new NotSupportedException();
case SeekOrigin.End:
this.Position = this.Length - offset;
break;
}
public void Write(byte[] buffer, int offset, int count)
=> throw new NotSupportedException();
return this.readerPosition;
}
/// <inheritdoc/>
public void Dispose()
{
if (!this.isDisposed)
@ -188,21 +184,18 @@ namespace SixLabors.ImageSharp.IO
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int GetPositionDifference(int p) => p - this.position;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool IsInReadBuffer(int p, out int index)
private bool IsInReadBuffer(long newPosition, out long index)
{
index = this.GetPositionDifference(p) + this.readBufferIndex;
index = newPosition - this.readerPosition + this.readBufferIndex;
return index > -1 && index < BufferLength;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void FillReadBuffer()
{
if (this.position != this.stream.Position)
if (this.readerPosition != this.stream.Position)
{
this.stream.Seek(this.position, SeekOrigin.Begin);
this.stream.Seek(this.readerPosition, SeekOrigin.Begin);
}
this.stream.Read(this.readBuffer, 0, BufferLength);
@ -215,35 +208,28 @@ namespace SixLabors.ImageSharp.IO
int n = this.GetCopyCount(count);
this.CopyBytes(buffer, offset, n);
this.position += n;
this.readerPosition += n;
this.readBufferIndex += n;
return n;
}
[MethodImpl(MethodImplOptions.NoInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int ReadToBufferViaCopySlow(byte[] buffer, int offset, int count)
{
// Refill our buffer then copy.
this.FillReadBuffer();
// return this.ReadToBufferViaCopyFast(buffer, offset, count);
int n = this.GetCopyCount(count);
this.CopyBytes(buffer, offset, n);
this.position += n;
this.readBufferIndex += n;
return n;
return this.ReadToBufferViaCopyFast(buffer, offset, count);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private int ReadToBufferDirectSlow(byte[] buffer, int offset, int count)
{
// Read to target but don't copy to our read buffer.
if (this.position != this.stream.Position)
if (this.readerPosition != this.stream.Position)
{
this.stream.Seek(this.position, SeekOrigin.Begin);
this.stream.Seek(this.readerPosition, SeekOrigin.Begin);
}
int n = this.stream.Read(buffer, offset, count);
@ -255,18 +241,18 @@ namespace SixLabors.ImageSharp.IO
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int GetCopyCount(int count)
{
int n = this.length - this.position;
long n = this.Length - this.readerPosition;
if (n > count)
{
n = count;
return count;
}
if (n < 0)
{
n = 0;
return 0;
}
return n;
return (int)n;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]

81
tests/ImageSharp.Benchmarks/Codecs/Jpeg/DoubleBufferedStreams.cs → tests/ImageSharp.Benchmarks/General/IO/BufferedStreams.cs

@ -6,10 +6,10 @@ using System.IO;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.IO;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
namespace SixLabors.ImageSharp.Benchmarks.IO
{
[Config(typeof(Config.ShortClr))]
public class DoubleBufferedStreams
public class BufferedStreams
{
private readonly byte[] buffer = CreateTestBytes();
private readonly byte[] chunk1 = new byte[2];
@ -21,14 +21,10 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
private MemoryStream stream4;
private MemoryStream stream5;
private MemoryStream stream6;
private MemoryStream stream7;
private MemoryStream stream8;
private DoubleBufferedStreamReader reader1;
private DoubleBufferedStreamReader reader2;
private BufferedReadStream bufferedStream1;
private BufferedReadStream bufferedStream2;
private BufferedReadStream2 bufferedStream3;
private BufferedReadStream2 bufferedStream4;
private BufferedReadStreamWrapper bufferedStreamWrap1;
private BufferedReadStreamWrapper bufferedStreamWrap2;
[GlobalSetup]
public void CreateStreams()
@ -39,31 +35,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
this.stream4 = new MemoryStream(this.buffer);
this.stream5 = new MemoryStream(this.buffer);
this.stream6 = new MemoryStream(this.buffer);
this.stream7 = new MemoryStream(this.buffer);
this.stream8 = new MemoryStream(this.buffer);
this.reader1 = new DoubleBufferedStreamReader(Configuration.Default.MemoryAllocator, this.stream2);
this.reader2 = new DoubleBufferedStreamReader(Configuration.Default.MemoryAllocator, this.stream2);
this.bufferedStream1 = new BufferedReadStream(this.stream5);
this.bufferedStream2 = new BufferedReadStream(this.stream6);
this.bufferedStream3 = new BufferedReadStream2(this.stream7);
this.bufferedStream4 = new BufferedReadStream2(this.stream8);
this.bufferedStream1 = new BufferedReadStream(this.stream3);
this.bufferedStream2 = new BufferedReadStream(this.stream4);
this.bufferedStreamWrap1 = new BufferedReadStreamWrapper(this.stream5);
this.bufferedStreamWrap2 = new BufferedReadStreamWrapper(this.stream6);
}
[GlobalCleanup]
public void DestroyStreams()
{
this.bufferedStream1?.Dispose();
this.bufferedStream2?.Dispose();
this.bufferedStreamWrap1?.Dispose();
this.bufferedStreamWrap2?.Dispose();
this.stream1?.Dispose();
this.stream2?.Dispose();
this.stream3?.Dispose();
this.stream4?.Dispose();
this.stream5?.Dispose();
this.stream6?.Dispose();
this.reader1?.Dispose();
this.reader2?.Dispose();
this.bufferedStream1?.Dispose();
this.bufferedStream2?.Dispose();
this.bufferedStream3?.Dispose();
this.bufferedStream4?.Dispose();
}
[Benchmark]
@ -82,10 +72,10 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
}
[Benchmark]
public int DoubleBufferedStreamRead()
public int BufferedReadStreamRead()
{
int r = 0;
DoubleBufferedStreamReader reader = this.reader2;
BufferedReadStream reader = this.bufferedStream1;
byte[] b = this.chunk2;
for (int i = 0; i < reader.Length / 2; i++)
@ -97,25 +87,10 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
}
[Benchmark]
public int BufferedStreamRead()
public int BufferedReadStreamWrapRead()
{
int r = 0;
BufferedReadStream reader = this.bufferedStream2;
byte[] b = this.chunk2;
for (int i = 0; i < reader.Length / 2; i++)
{
r += reader.Read(b, 0, 2);
}
return r;
}
[Benchmark]
public int BufferedStreamWrapRead()
{
int r = 0;
BufferedReadStream2 reader = this.bufferedStream3;
BufferedReadStreamWrapper reader = this.bufferedStreamWrap1;
byte[] b = this.chunk2;
for (int i = 0; i < reader.Length / 2; i++)
@ -130,7 +105,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
public int StandardStreamReadByte()
{
int r = 0;
Stream stream = this.stream1;
Stream stream = this.stream2;
for (int i = 0; i < stream.Length; i++)
{
@ -141,21 +116,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
}
[Benchmark]
public int DoubleBufferedStreamReadByte()
{
int r = 0;
DoubleBufferedStreamReader reader = this.reader1;
for (int i = 0; i < reader.Length; i++)
{
r += reader.ReadByte();
}
return r;
}
[Benchmark]
public int BufferedStreamReadByte()
public int BufferedReadStreamReadByte()
{
int r = 0;
BufferedReadStream reader = this.bufferedStream2;
@ -169,10 +130,10 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
}
[Benchmark]
public int BufferedStreamWrapReadByte()
public int BufferedReadStreamWrapReadByte()
{
int r = 0;
BufferedReadStream2 reader = this.bufferedStream4;
BufferedReadStreamWrapper reader = this.bufferedStreamWrap2;
for (int i = 0; i < reader.Length; i++)
{
@ -197,7 +158,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
private static byte[] CreateTestBytes()
{
var buffer = new byte[DoubleBufferedStreamReader.ChunkLength * 3];
var buffer = new byte[BufferedReadStream.BufferLength * 3];
var random = new Random();
random.NextBytes(buffer);

1
tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj

@ -31,7 +31,6 @@
<!-- Exclude benchmarks using internals, in case of unsigned benchmark execution: -->
<ItemGroup Condition="'$(SignAssembly)' == 'false'">
<Compile Remove="Codecs\Jpeg\BlockOperations\**" />
<Compile Remove="Codecs\Jpeg\DoubleBufferedStreams.cs" />
<Compile Remove="Codecs\Jpeg\YCbCrColorConversion.cs" />
<Compile Remove="Codecs\Jpeg\DecodeJpegParseStreamOnly.cs" />
<Compile Remove="Color\Bulk\**" />

16
tests/ImageSharp.Tests/IO/BufferedReadStreamTests.cs

@ -199,6 +199,22 @@ namespace SixLabors.ImageSharp.Tests.IO
}
}
[Fact]
public void BufferedStreamReadsCanReadAllAsSingleByteFromOrigin()
{
using (MemoryStream stream = this.CreateTestStream())
{
byte[] expected = stream.ToArray();
using (var reader = new BufferedReadStream(stream))
{
for (int i = 0; i < expected.Length; i++)
{
Assert.Equal(expected[i], reader.ReadByte());
}
}
}
}
private MemoryStream CreateTestStream(int length = BufferedReadStream.BufferLength * 3)
{
var buffer = new byte[length];

176
tests/ImageSharp.Tests/IO/DoubleBufferedStreamReaderTests.cs

@ -1,176 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using Xunit;
namespace SixLabors.ImageSharp.Tests.IO
{
public class DoubleBufferedStreamReaderTests
{
private readonly MemoryAllocator allocator = Configuration.Default.MemoryAllocator;
[Fact]
public void DoubleBufferedStreamReaderCanReadSingleByteFromOrigin()
{
using (MemoryStream stream = this.CreateTestStream())
{
byte[] expected = stream.ToArray();
var reader = new DoubleBufferedStreamReader(this.allocator, stream);
Assert.Equal(expected[0], reader.ReadByte());
// We've read a whole chunk but increment by 1 in our reader.
Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength);
Assert.Equal(1, reader.Position);
}
}
[Fact]
public void DoubleBufferedStreamReaderCanReadSingleByteFromOffset()
{
using (MemoryStream stream = this.CreateTestStream())
{
byte[] expected = stream.ToArray();
const int offset = 5;
var reader = new DoubleBufferedStreamReader(this.allocator, stream);
reader.Position = offset;
Assert.Equal(expected[offset], reader.ReadByte());
// We've read a whole chunk but increment by 1 in our reader.
Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength + offset);
Assert.Equal(offset + 1, reader.Position);
}
}
[Fact]
public void DoubleBufferedStreamReaderCanReadSubsequentSingleByteCorrectly()
{
using (MemoryStream stream = this.CreateTestStream())
{
byte[] expected = stream.ToArray();
var reader = new DoubleBufferedStreamReader(this.allocator, stream);
for (int i = 0; i < expected.Length; i++)
{
Assert.Equal(expected[i], reader.ReadByte());
Assert.Equal(i + 1, reader.Position);
if (i < DoubleBufferedStreamReader.ChunkLength)
{
Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength);
}
else if (i >= DoubleBufferedStreamReader.ChunkLength && i < DoubleBufferedStreamReader.ChunkLength * 2)
{
// We should have advanced to the second chunk now.
Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength * 2);
}
else
{
// We should have advanced to the third chunk now.
Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength * 3);
}
}
}
}
[Fact]
public void DoubleBufferedStreamReaderCanReadMultipleBytesFromOrigin()
{
using (MemoryStream stream = this.CreateTestStream())
{
var buffer = new byte[2];
byte[] expected = stream.ToArray();
var reader = new DoubleBufferedStreamReader(this.allocator, stream);
Assert.Equal(2, reader.Read(buffer, 0, 2));
Assert.Equal(expected[0], buffer[0]);
Assert.Equal(expected[1], buffer[1]);
// We've read a whole chunk but increment by the buffer length in our reader.
Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength);
Assert.Equal(buffer.Length, reader.Position);
}
}
[Fact]
public void DoubleBufferedStreamReaderCanReadSubsequentMultipleByteCorrectly()
{
using (MemoryStream stream = this.CreateTestStream())
{
var buffer = new byte[2];
byte[] expected = stream.ToArray();
var reader = new DoubleBufferedStreamReader(this.allocator, stream);
for (int i = 0, o = 0; i < expected.Length / 2; i++, o += 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);
int offset = i * 2;
if (offset < DoubleBufferedStreamReader.ChunkLength)
{
Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength);
}
else if (offset >= DoubleBufferedStreamReader.ChunkLength && offset < DoubleBufferedStreamReader.ChunkLength * 2)
{
// We should have advanced to the second chunk now.
Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength * 2);
}
else
{
// We should have advanced to the third chunk now.
Assert.Equal(stream.Position, DoubleBufferedStreamReader.ChunkLength * 3);
}
}
}
}
[Fact]
public void DoubleBufferedStreamReaderCanSkip()
{
using (MemoryStream stream = this.CreateTestStream())
{
byte[] expected = stream.ToArray();
var reader = new DoubleBufferedStreamReader(this.allocator, stream);
int skip = 50;
int plusOne = 1;
int skip2 = DoubleBufferedStreamReader.ChunkLength;
// Skip
reader.Skip(skip);
Assert.Equal(skip, reader.Position);
Assert.Equal(stream.Position, reader.Position);
// Read
Assert.Equal(expected[skip], reader.ReadByte());
// Skip Again
reader.Skip(skip2);
// First Skip + First Read + Second Skip
int position = skip + plusOne + skip2;
Assert.Equal(position, reader.Position);
Assert.Equal(stream.Position, reader.Position);
Assert.Equal(expected[position], reader.ReadByte());
}
}
private MemoryStream CreateTestStream()
{
var buffer = new byte[DoubleBufferedStreamReader.ChunkLength * 3];
var random = new Random();
random.NextBytes(buffer);
return new MemoryStream(buffer);
}
}
}
Loading…
Cancel
Save