diff --git a/samples/LowAllocationWebServer/LowAllocationWebServerLibrary/BufferSequence.cs b/samples/LowAllocationWebServer/LowAllocationWebServerLibrary/BufferSequence.cs index 99f1cb4bc04..c78b0b12a91 100644 --- a/samples/LowAllocationWebServer/LowAllocationWebServerLibrary/BufferSequence.cs +++ b/samples/LowAllocationWebServer/LowAllocationWebServerLibrary/BufferSequence.cs @@ -29,7 +29,7 @@ public BufferSequence(int desiredSize = DefaultBufferSize) public Span Free => new Span(_array, _written, _array.Length - _written); - public IMemoryList Rest => _next; + public IMemoryList Next => _next; public int WrittenByteCount => _written; diff --git a/src/System.Buffers.Experimental/System/Buffers/BufferExtensions.cs b/src/System.Buffers.Experimental/System/Buffers/BufferExtensions.cs index c331fef7163..244f67c88ce 100644 --- a/src/System.Buffers.Experimental/System/Buffers/BufferExtensions.cs +++ b/src/System.Buffers.Experimental/System/Buffers/BufferExtensions.cs @@ -9,6 +9,118 @@ namespace System.Buffers { + + public static class MemoryListExtensions + { + // span creation helpers: + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long IndexOf(this IMemoryList list, ReadOnlySpan value) + { + var first = list.Memory.Span; + var index = first.IndexOf(value); + if (index != -1) return index; + + var rest = list.Next; + if (rest == null) return -1; + + return IndexOfStraddling(first, list.Next, value); + } + + public static int CopyTo(this IMemoryList list, Span destination) + { + var current = list.Memory.Span; + int copied = 0; + + while (destination.Length > 0) + { + if (current.Length >= destination.Length) + { + current.Slice(0, destination.Length).CopyTo(destination); + copied += destination.Length; + return copied; + } + else + { + current.CopyTo(destination); + copied += current.Length; + destination = destination.Slice(current.Length); + } + } + return copied; + } + + public static Position PositionOf(this IMemoryList list, byte value) + { + while (list != null) + { + var current = list.Memory.Span; + var index = current.IndexOf(value); + if (index != -1) return Position.Create(list, index); + list = list.Next; + } + return Position.End; + } + + // TODO (pri 3): I am pretty sure this whole routine can be written much better + + // searches values that potentially straddle between first and rest + internal static long IndexOfStraddling(this ReadOnlySpan first, IMemoryList rest, ReadOnlySpan value) + { + Debug.Assert(first.IndexOf(value) == -1); + if (rest == null) return -1; + + // we only need to search the end of the first buffer. More precisely, only up to value.Length - 1 bytes in the first buffer + // The other bytes in first, were already search and presumably did not match + int bytesToSkipFromFirst = 0; + if (first.Length > value.Length - 1) + { + bytesToSkipFromFirst = first.Length - value.Length - 1; + } + + // now that we know how many bytes we need to skip, create slice of first buffer with bytes that need to be searched. + ReadOnlySpan bytesToSearchAgain; + if (bytesToSkipFromFirst > 0) + { + bytesToSearchAgain = first.Slice(bytesToSkipFromFirst); + } + else + { + bytesToSearchAgain = first; + } + + long index; + + // now combine the bytes from the end of the first buffer with bytes in the rest, and serarch the combined buffer + // this check is a small optimization: if the first byte from the value does not exist in the bytesToSearchAgain, there is no reason to combine + if (bytesToSearchAgain.IndexOf(value[0]) != -1) + { + var combinedBufferLength = value.Length << 1; + var combined = combinedBufferLength < 128 ? + stackalloc byte[combinedBufferLength] : + // TODO (pri 3): I think this could be eliminated by chunking values + new byte[combinedBufferLength]; + + bytesToSearchAgain.CopyTo(combined); + int combinedLength = bytesToSearchAgain.Length + rest.CopyTo(combined.Slice(bytesToSearchAgain.Length)); + combined = combined.Slice(0, combinedLength); + + if (combined.Length < value.Length) return -1; + + index = combined.IndexOf(value); + if (index != -1) + { + return index + bytesToSkipFromFirst; + } + } + + // try to find the bytes in _rest + index = rest.IndexOf(value); + if (index != -1) return first.Length + index; + + return -1; + } + } + public static class BufferExtensions { const int stackLength = 32; @@ -112,79 +224,6 @@ public static void Pipe(this IBufferOperation transformation, ReadOnlyBytes sour return; } - // span creation helpers: - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static long IndexOf(this IMemoryList sequence, ReadOnlySpan value) - { - var first = sequence.Memory.Span; - var index = first.IndexOf(value); - if (index != -1) return index; - - var rest = sequence.Rest; - if (rest == null) return -1; - - return IndexOfStraddling(first, sequence.Rest, value); - } - - // TODO (pri 3): I am pretty sure this whole routine can be written much better - - // searches values that potentially straddle between first and rest - internal static long IndexOfStraddling(this ReadOnlySpan first, IMemoryList rest, ReadOnlySpan value) - { - Debug.Assert(first.IndexOf(value) == -1); - if (rest == null) return -1; - - // we only need to search the end of the first buffer. More precisely, only up to value.Length - 1 bytes in the first buffer - // The other bytes in first, were already search and presumably did not match - int bytesToSkipFromFirst = 0; - if (first.Length > value.Length - 1) - { - bytesToSkipFromFirst = first.Length - value.Length - 1; - } - - // now that we know how many bytes we need to skip, create slice of first buffer with bytes that need to be searched. - ReadOnlySpan bytesToSearchAgain; - if (bytesToSkipFromFirst > 0) - { - bytesToSearchAgain = first.Slice(bytesToSkipFromFirst); - } - else - { - bytesToSearchAgain = first; - } - - long index; - - // now combine the bytes from the end of the first buffer with bytes in the rest, and serarch the combined buffer - // this check is a small optimization: if the first byte from the value does not exist in the bytesToSearchAgain, there is no reason to combine - if (bytesToSearchAgain.IndexOf(value[0]) != -1) - { - var combinedBufferLength = value.Length << 1; - var combined = combinedBufferLength < 128 ? - stackalloc byte[combinedBufferLength] : - // TODO (pri 3): I think this could be eliminated by chunking values - new byte[combinedBufferLength]; - - bytesToSearchAgain.CopyTo(combined); - int combinedLength = bytesToSearchAgain.Length + rest.CopyTo(combined.Slice(bytesToSearchAgain.Length)); - combined = combined.Slice(0, combinedLength); - - if (combined.Length < value.Length) return -1; - - index = combined.IndexOf(value); - if (index != -1) - { - return index + bytesToSkipFromFirst; - } - } - - // try to find the bytes in _rest - index = rest.IndexOf(value); - if (index != -1) return first.Length + index; - - return -1; - } - public static bool TryIndicesOf(this Span buffer, byte value, Span indices, out int numberOfIndices) { var length = buffer.Length; diff --git a/src/System.Buffers.Experimental/System/Buffers/Sequences/MemoryList.cs b/src/System.Buffers.Experimental/System/Buffers/Sequences/MemoryList.cs index c50a9817ca1..6ef09703bd4 100644 --- a/src/System.Buffers.Experimental/System/Buffers/Sequences/MemoryList.cs +++ b/src/System.Buffers.Experimental/System/Buffers/Sequences/MemoryList.cs @@ -2,8 +2,6 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Collections.Sequences; -using System.Diagnostics; -using System.Runtime.CompilerServices; using System.Text; namespace System.Buffers @@ -37,7 +35,7 @@ public MemoryList Append(Memory bytes) public Memory Memory => _data; - public IMemoryList Rest => _next; + public IMemoryList Next => _next; public long VirtualIndex => _virtualIndex; diff --git a/src/System.Buffers.Experimental/System/Buffers/Sequences/ReadOnlyBytes.cs b/src/System.Buffers.Experimental/System/Buffers/Sequences/ReadOnlyBytes.cs index 1aef6b30269..9b0a41e9feb 100644 --- a/src/System.Buffers.Experimental/System/Buffers/Sequences/ReadOnlyBytes.cs +++ b/src/System.Buffers.Experimental/System/Buffers/Sequences/ReadOnlyBytes.cs @@ -98,7 +98,7 @@ public ReadOnlyBytes Slice(long index, long length) break; } index -= m.Length; - sl = sl.Rest; + sl = sl.Next; } var el = sl; @@ -117,7 +117,7 @@ public ReadOnlyBytes Slice(long index, long length) { return new ReadOnlyBytes(sl, (int)index, el, m.Length); } - el = el.Rest; + el = el.Next; } default: throw new NotImplementedException(); @@ -293,7 +293,7 @@ public bool TryGet(ref Position position, out ReadOnlyMemory item, bool ad { var first = _start as IMemoryList; item = first.Memory.Slice(_startIndex); - if (advance) position = Position.Create(first.Rest); + if (advance) position = Position.Create(first.Next); if (ReferenceEquals(_end, _start)){ item = item.Slice(0, (int)Length); if (advance) position = Position.End; @@ -310,7 +310,7 @@ public bool TryGet(ref Position position, out ReadOnlyMemory item, bool ad } else { - if (advance) position = Position.Create(node.Rest); + if (advance) position = Position.Create(node.Next); } return true; } diff --git a/src/System.Buffers.Experimental/System/Buffers/Sequences/ReadWriteBytes.cs b/src/System.Buffers.Experimental/System/Buffers/Sequences/ReadWriteBytes.cs index 4eaed0300d9..6e63cc06df2 100644 --- a/src/System.Buffers.Experimental/System/Buffers/Sequences/ReadWriteBytes.cs +++ b/src/System.Buffers.Experimental/System/Buffers/Sequences/ReadWriteBytes.cs @@ -2,8 +2,6 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Collections.Sequences; -using System.Diagnostics; -using System.Runtime.CompilerServices; namespace System.Buffers { @@ -97,7 +95,7 @@ public ReadWriteBytes Slice(long index, long length) break; } index -= m.Length; - sl = sl.Rest; + sl = sl.Next; } var el = sl; @@ -116,7 +114,7 @@ public ReadWriteBytes Slice(long index, long length) { return new ReadWriteBytes(sl, (int)index, el, m.Length); } - el = el.Rest; + el = el.Next; } default: throw new NotImplementedException(); @@ -293,7 +291,7 @@ public bool TryGet(ref Position position, out Memory item, bool advance = { var first = _start as IMemoryList; item = first.Memory.Slice(_startIndex); - if (advance) position = Position.Create(first.Rest); + if (advance) position = Position.Create(first.Next); if (ReferenceEquals(_end, _start)) { item = item.Slice(0, (int)Length); @@ -311,7 +309,7 @@ public bool TryGet(ref Position position, out Memory item, bool advance = } else { - if (advance) position = Position.Create(node.Rest); + if (advance) position = Position.Create(node.Next); } return true; } diff --git a/src/System.Buffers.Experimental/System/Buffers/Sequences/IMemoryList.cs b/src/System.Buffers.Primitives/System/Collections/IMemoryList.cs similarity index 51% rename from src/System.Buffers.Experimental/System/Buffers/Sequences/IMemoryList.cs rename to src/System.Buffers.Primitives/System/Collections/IMemoryList.cs index f0d9f7ba12e..c06b23637af 100644 --- a/src/System.Buffers.Experimental/System/Buffers/Sequences/IMemoryList.cs +++ b/src/System.Buffers.Primitives/System/Collections/IMemoryList.cs @@ -1,18 +1,14 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System.Collections.Sequences; - -namespace System.Buffers +namespace System.Collections.Sequences { - public interface IMemoryList : ISequence>, ISequence> + public interface IMemoryList { Memory Memory { get; } - IMemoryList Rest { get; } + IMemoryList Next { get; } long VirtualIndex { get; } - - int CopyTo(Span buffer); } } diff --git a/src/System.IO.Pipelines/System/IO/Pipelines/BufferSegment.cs b/src/System.IO.Pipelines/System/IO/Pipelines/BufferSegment.cs index e2d11397152..ad359d46d5a 100644 --- a/src/System.IO.Pipelines/System/IO/Pipelines/BufferSegment.cs +++ b/src/System.IO.Pipelines/System/IO/Pipelines/BufferSegment.cs @@ -2,20 +2,12 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Buffers; +using System.Collections.Sequences; using System.Diagnostics; using System.Text; namespace System.IO.Pipelines { - public interface IMemoryList - { - Memory Memory { get; } - - IMemoryList Next { get; } - - long VirtualIndex { get; } - } - internal class BufferSegment: IMemoryList { /// diff --git a/src/System.IO.Pipelines/System/IO/Pipelines/ReadCursorOperations.cs b/src/System.IO.Pipelines/System/IO/Pipelines/ReadCursorOperations.cs index d12551a6707..a1a67a47213 100644 --- a/src/System.IO.Pipelines/System/IO/Pipelines/ReadCursorOperations.cs +++ b/src/System.IO.Pipelines/System/IO/Pipelines/ReadCursorOperations.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Buffers; +using System.Collections.Sequences; using System.Runtime.CompilerServices; namespace System.IO.Pipelines diff --git a/src/System.IO.Pipelines/System/IO/Pipelines/ReadableBuffer.cs b/src/System.IO.Pipelines/System/IO/Pipelines/ReadableBuffer.cs index 284b3a4c172..d3e265d2a14 100644 --- a/src/System.IO.Pipelines/System/IO/Pipelines/ReadableBuffer.cs +++ b/src/System.IO.Pipelines/System/IO/Pipelines/ReadableBuffer.cs @@ -2,9 +2,8 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Buffers; -using System.Collections.Generic; +using System.Collections.Sequences; using System.Diagnostics; -using System.Runtime.CompilerServices; using System.Text; namespace System.IO.Pipelines diff --git a/tests/System.Buffers.Experimental.Tests/SequenceExtensionsTests.cs b/tests/System.Buffers.Experimental.Tests/SequenceExtensionsTests.cs index 329ed9438bf..cc3531487a3 100644 --- a/tests/System.Buffers.Experimental.Tests/SequenceExtensionsTests.cs +++ b/tests/System.Buffers.Experimental.Tests/SequenceExtensionsTests.cs @@ -79,7 +79,7 @@ public void SequencePositionOfMultiSegment() { var value = (byte)(i + 1); - var listPosition = Sequence.PositionOf(first, value); + var listPosition = MemoryListExtensions.PositionOf(first, value); var (node, index) = listPosition.Get>(); if (!listPosition.IsEnd)