diff --git a/scripts/PerfHarness/PerfHarness.csproj b/scripts/PerfHarness/PerfHarness.csproj index b0481ffeb45..f20842d91b4 100644 --- a/scripts/PerfHarness/PerfHarness.csproj +++ b/scripts/PerfHarness/PerfHarness.csproj @@ -1,7 +1,7 @@  - netcoreapp1.0 + netcoreapp1.1 portable PerfHarness Exe diff --git a/src/System.Buffers.Experimental/System.Buffers.Experimental.csproj b/src/System.Buffers.Experimental/System.Buffers.Experimental.csproj index 68093f5e0bb..8ed8a853784 100644 --- a/src/System.Buffers.Experimental/System.Buffers.Experimental.csproj +++ b/src/System.Buffers.Experimental/System.Buffers.Experimental.csproj @@ -13,5 +13,6 @@ + diff --git a/src/System.Buffers.Experimental/System/Buffers/BufferExtensions.cs b/src/System.Buffers.Experimental/System/Buffers/BufferExtensions.cs index 5e2f66363c7..b8f10a949e4 100644 --- a/src/System.Buffers.Experimental/System/Buffers/BufferExtensions.cs +++ b/src/System.Buffers.Experimental/System/Buffers/BufferExtensions.cs @@ -3,6 +3,7 @@ using System.Collections.Sequences; using System.Diagnostics; +using System.Numerics; using System.Runtime.CompilerServices; namespace System.Buffers @@ -117,5 +118,107 @@ internal static int IndexOfStraddling(this ReadOnlySpan first, IReadOnlyMe return -1; } + + static readonly int s_longSize = Vector.Count; + static readonly int s_byteSize = Vector.Count; + + public static int IndexOfVectorized(this Span buffer, byte value) + { + Debug.Assert(s_longSize == 4 || s_longSize == 2); + + var byteSize = s_byteSize; + + if (buffer.Length < byteSize * 2 || !Vector.IsHardwareAccelerated) return buffer.IndexOf(value); + + Vector match = new Vector(value); + var vectors = buffer.NonPortableCast>(); + var zero = Vector.Zero; + + for (int vectorIndex = 0; vectorIndex < vectors.Length; vectorIndex++) + { + var vector = vectors.GetItem(vectorIndex); + var result = Vector.Equals(vector, match); + if (result != zero) + { + var longer = Vector.AsVectorUInt64(result); + Debug.Assert(s_longSize == 4 || s_longSize == 2); + + var candidate = longer[0]; + if (candidate != 0) return vectorIndex * byteSize + IndexOf(candidate); + candidate = longer[1]; + if (candidate != 0) return 8 + vectorIndex * byteSize + IndexOf(candidate); + if (s_longSize == 4) + { + candidate = longer[2]; + if (candidate != 0) return 16 + vectorIndex * byteSize + IndexOf(candidate); + candidate = longer[3]; + if (candidate != 0) return 24 + vectorIndex * byteSize + IndexOf(candidate); + } + } + } + + var processed = vectors.Length * byteSize; + var index = buffer.Slice(processed).IndexOf(value); + if (index == -1) return -1; + return index + processed; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static int IndexOfVectorized(this ReadOnlySpan buffer, byte value) + { + Debug.Assert(s_longSize == 4 || s_longSize == 2); + + var byteSize = s_byteSize; + + if (buffer.Length < byteSize * 2 || !Vector.IsHardwareAccelerated) return buffer.IndexOf(value); + + Vector match = new Vector(value); + var vectors = buffer.NonPortableCast>(); + var zero = Vector.Zero; + + for (int vectorIndex = 0; vectorIndex < vectors.Length; vectorIndex++) + { + var vector = vectors[vectorIndex]; + var result = Vector.Equals(vector, match); + if (result != zero) + { + var longer = Vector.AsVectorUInt64(result); + var candidate = longer[0]; + if (candidate != 0) return vectorIndex * byteSize + IndexOf(candidate); + candidate = longer[1]; + if (candidate != 0) return 8 + vectorIndex * byteSize + IndexOf(candidate); + if (s_longSize == 4) + { + candidate = longer[2]; + if (candidate != 0) return 16 + vectorIndex * byteSize + IndexOf(candidate); + candidate = longer[3]; + if (candidate != 0) return 24 + vectorIndex * byteSize + IndexOf(candidate); + } + } + } + + var processed = vectors.Length * byteSize; + var index = buffer.Slice(processed).IndexOf(value); + if (index == -1) return -1; + return index + processed; + } + + // used by IndexOfVectorized + static int IndexOf(ulong next) + { + // Flag least significant power of two bit + var powerOfTwoFlag = (next ^ (next - 1)); + // Shift all powers of two into the high byte and extract + var foundByteIndex = (int)((powerOfTwoFlag * _xorPowerOfTwoToHighByte) >> 57); + return foundByteIndex; + } + + const ulong _xorPowerOfTwoToHighByte = (0x07ul | + 0x06ul << 8 | + 0x05ul << 16 | + 0x04ul << 24 | + 0x03ul << 32 | + 0x02ul << 40 | + 0x01ul << 48) + 1; } } \ No newline at end of file diff --git a/tests/Benchmarks/Benchmarks.csproj b/tests/Benchmarks/Benchmarks.csproj index 62fa2d33b56..e8494532356 100644 --- a/tests/Benchmarks/Benchmarks.csproj +++ b/tests/Benchmarks/Benchmarks.csproj @@ -1,6 +1,6 @@ - netcoreapp1.0 + netcoreapp1.1 False ../../tools/test_key.snk true diff --git a/tests/Benchmarks/IndexOf.cs b/tests/Benchmarks/IndexOf.cs new file mode 100644 index 00000000000..9ce0431a444 --- /dev/null +++ b/tests/Benchmarks/IndexOf.cs @@ -0,0 +1,58 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Xunit.Performance; +using System; +using System.Buffers; +using System.Numerics; +using System.Text; + +public class IndexOfBench +{ + static int s_bufferLength = 1000; + static byte[] s_buffer = new byte[s_bufferLength]; + static int s_loops = 1000; + + static IndexOfBench() + { + s_buffer[s_bufferLength - 100] = 255; + } + + [Benchmark] + static int SpanIndexOf() + { + Span buffer = s_buffer; + int index = 0; + foreach (var iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + for(int i=0; i buffer = s_buffer; + int index = 0; + foreach (var iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + for(int i=0; i span = buffer; + Assert.Equal(0, span.IndexOfVectorized(1)); + Assert.Equal(len/2, span.IndexOfVectorized(2)); + Assert.Equal(len-1, span.IndexOfVectorized(3)); + Assert.Equal(-1, span.IndexOfVectorized(4)); + } + + [Fact] + public void ReadOnlySpanIndexOf() + { + int len = 10000; + byte[] buffer = new byte[len]; + buffer[0] = 1; + buffer[len / 2] = 2; + buffer[len - 1] = 3; + + ReadOnlySpan span = buffer; + Assert.Equal(0, span.IndexOfVectorized(1)); + Assert.Equal(len/2, span.IndexOfVectorized(2)); + Assert.Equal(len-1, span.IndexOfVectorized(3)); + Assert.Equal(-1, span.IndexOfVectorized(4)); + } + + [Fact] + public void EmptySpanIndexOf() + { + int len = 0; + byte[] buffer = new byte[len]; + Span span = buffer; + Assert.Equal(-1, span.IndexOfVectorized(4)); + } + + [Fact] + public void EmptyReadOnlySpanIndexOf() + { + int len = 10000; + byte[] buffer = new byte[len]; + buffer[0] = 1; + buffer[len / 2] = 2; + buffer[len - 1] = 3; + + ReadOnlySpan span = buffer; + Assert.Equal(-1, span.IndexOfVectorized(4)); + } + } +} \ No newline at end of file diff --git a/tests/System.Text.Formatting.Tests/System.Text.Formatting.Tests.csproj b/tests/System.Text.Formatting.Tests/System.Text.Formatting.Tests.csproj index 99d39cc2c4e..669b8be1b01 100644 --- a/tests/System.Text.Formatting.Tests/System.Text.Formatting.Tests.csproj +++ b/tests/System.Text.Formatting.Tests/System.Text.Formatting.Tests.csproj @@ -16,6 +16,7 @@ +