Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/System.Memory/ref/System.Memory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ public static class SpanExtensions
{
public static int IndexOf<T>(this Span<T> span, T value) where T:struct, IEquatable<T> { throw null; }
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is the approved API proposal for this? What about the other overloads?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is just a temporary hack until the Span codegen is fixed, you should keep it in corefxlab.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I agree corfxlab might be a better place for it. corfx APIs should represent what we are confident in shipping.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fine, will slicing + indexof ever be as fast as passing an index and count? I have some doubts about that.

while (span.Length > 0)
{
      int index = span.IndexOf((byte)'\n');
      if (index == -1) break;
      var line = span.Slice(0, index);
      span = span.Slice(index + 1);
}

VS

int index = 0;
while (index < span.Length)
{
      int lineIndex = span.IndexOf((byte)'\n', index, span.Length - index);
      if (lineIndex == -1) break;
      var line = span.Slice(index, lineIndex - index + 1);
      index += length.Length
}

Will that ever be within the ballpark?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it should be pretty equivalent - once the JIT team implements the planned struct enregistration that handles Span well.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, I look forward to seeing this. @ahsonkhan Can you make sure we have a benchmark for this scenario? It's pretty fundamental.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have created it in the PR in corefxlab:

result = bytes.IndexOf(lookupVal, startIndex, count);

VS.

result = bytes.Slice(startIndex, count).IndexOf(lookupVal);

public static int IndexOf(this Span<byte> span, byte value) { throw null; }
public static int IndexOf(this Span<byte> span, int index, int count, byte value) { throw null; }
public static int IndexOf(this Span<char> span, char value) { throw null; }

public static bool SequenceEqual<T>(this Span<T> first, ReadOnlySpan<T> second) where T:struct, IEquatable<T> { throw null; }
Expand All @@ -99,6 +100,7 @@ public static class SpanExtensions

public static int IndexOf<T>(this ReadOnlySpan<T> span, T value) where T : struct, IEquatable<T> { throw null; }
public static int IndexOf(this ReadOnlySpan<byte> span, byte value) { throw null; }
public static int IndexOf(this ReadOnlySpan<byte> span, int index, int count, byte value) { throw null; }
public static int IndexOf(this ReadOnlySpan<char> span, char value) { throw null; }

public static bool SequenceEqual<T>(this ReadOnlySpan<T> first, ReadOnlySpan<T> second) where T : struct, IEquatable<T> { throw null; }
Expand Down
27 changes: 27 additions & 0 deletions src/System.Memory/src/System/SpanExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,19 @@ public static int IndexOf(this Span<byte> span, byte value)
return SpanHelpers.IndexOf(ref span.DangerousGetPinnableReference(), value, span.Length);
}

/// <summary>
/// Searches for the specified value starting at the specified index and returns the index of its first occurrence. If not found, returns -1.
/// </summary>
/// <param name="span">The span to search.</param>
/// <param name="index">The index within the span from where to start the search.</param>
/// <param name="count">The number of bytes to search starting from the index.</param>
/// <param name="value">The value to search for.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int IndexOf(this Span<byte> span, int index, int count, byte value)
{
return SpanHelpers.IndexOf(ref span.DangerousGetPinnableReference(), value, index, count, span.Length);
}

/// <summary>
/// Searches for the specified value and returns the index of its first occurrence. If not found, returns -1.
/// </summary>
Expand Down Expand Up @@ -134,6 +147,20 @@ public static int IndexOf(this ReadOnlySpan<byte> span, byte value)
return SpanHelpers.IndexOf(ref span.DangerousGetPinnableReference(), value, span.Length);
}


/// <summary>
/// Searches for the specified value starting at the specified index and returns the index of its first occurrence. If not found, returns -1.
/// </summary>
/// <param name="span">The span to search.</param>
/// <param name="index">The index within the span from where to start the search.</param>
/// <param name="count">The number of bytes to search starting from the index.</param>
/// <param name="value">The value to search for.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int IndexOf(this ReadOnlySpan<byte> span, int index, int count, byte value)
{
return SpanHelpers.IndexOf(ref span.DangerousGetPinnableReference(), value, index, count, span.Length);
}

/// <summary>
/// Searches for the specified value and returns the index of its first occurrence. If not found, returns -1.
/// </summary>
Expand Down
63 changes: 63 additions & 0 deletions src/System.Memory/src/System/SpanHelpers.byte.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,69 @@ public static int IndexOf(ref byte searchSpace, byte value, int length)
return -1;
}

public static int IndexOf(ref byte searchSpace, byte value, int startIndex, int count, int spanLength)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not think it makes sense to replicate the worker method for this - you should use the existing public static int IndexOf(ref byte searchSpace, byte value, int length) method.

{
Debug.Assert(count >= 0);
Debug.Assert(spanLength >= 0);
Debug.Assert(startIndex >= 0);

int index = startIndex;

if (startIndex >= spanLength || count == 0)
{
return -1;
}

int remainingLength = Math.Min(count, spanLength - startIndex);

Unsafe.Add(ref searchSpace, startIndex);

while (remainingLength >= 8)
{
if (value == Unsafe.Add(ref searchSpace, ++index))
return index;
if (value == Unsafe.Add(ref searchSpace, ++index))
return index;
if (value == Unsafe.Add(ref searchSpace, ++index))
return index;
if (value == Unsafe.Add(ref searchSpace, ++index))
return index;
if (value == Unsafe.Add(ref searchSpace, ++index))
return index;
if (value == Unsafe.Add(ref searchSpace, ++index))
return index;
if (value == Unsafe.Add(ref searchSpace, ++index))
return index;
if (value == Unsafe.Add(ref searchSpace, ++index))
return index;

remainingLength -= 8;
}

while (remainingLength >= 4)
{
if (value == Unsafe.Add(ref searchSpace, ++index))
return index;
if (value == Unsafe.Add(ref searchSpace, ++index))
return index;
if (value == Unsafe.Add(ref searchSpace, ++index))
return index;
if (value == Unsafe.Add(ref searchSpace, ++index))
return index;

remainingLength -= 4;
}

while (remainingLength > 0)
{
if (value == Unsafe.Add(ref searchSpace, ++index))
return index;

remainingLength--;
}
return -1;
}

public static bool SequenceEqual(ref byte first, ref byte second, int length)
{
Debug.Assert(length >= 0);
Expand Down
81 changes: 81 additions & 0 deletions src/System.Memory/tests/ReadOnlySpan/IndexOf.byte.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,86 @@ public static void MakeSureNoChecksGoOutOfRange_Byte()
Assert.Equal(-1, index);
}
}

[Fact]
public static void TestMatchWithIndexAndCount_Byte()
{
int length = 100;
byte[] a = new byte[length];
a[50] = 99;
ReadOnlySpan<byte> span = new ReadOnlySpan<byte>(a);

int idx = span.IndexOf(45, 10, 99);
Assert.Equal(50, idx);
}

[Fact]
public static void TestMatchWithIndexAndCountGreaterThanLength_Byte()
{
int length = 100;
byte[] a = new byte[length];
a[50] = 99;
ReadOnlySpan<byte> span = new ReadOnlySpan<byte>(a);

int idx = span.IndexOf(45, 75, 99);
Assert.Equal(50, idx);
}

[Fact]
public static void TestMatchWithCountGreaterThanLength_Byte()
{
int length = 100;
byte[] a = new byte[length];
a[50] = 99;
ReadOnlySpan<byte> span = new ReadOnlySpan<byte>(a);

int idx = span.IndexOf(0, 105, 99);
Assert.Equal(50, idx);
}

[Fact]
public static void TestNoMatchWithCountGreaterThanLength_Byte()
{
int length = 100;
byte[] a = new byte[length];
a[50] = 99;
ReadOnlySpan<byte> span = new ReadOnlySpan<byte>(a);

int idx = span.IndexOf(0, 105, 5);
Assert.Equal(-1, idx);
}

[Fact]
public static void TestNoMatchWithIndexAndCount_Byte()
{
int length = 100;
byte[] a = new byte[length];
a[50] = 99;
ReadOnlySpan<byte> span = new ReadOnlySpan<byte>(a);

int idx = span.IndexOf(45, 3, 99);
Assert.Equal(-1, idx);
}

[Fact]
public static void StartIndexTooLargeIndexOf_Byte()
{
int length = 100;
byte[] a = new byte[length];
a[50] = 99;
ReadOnlySpan<byte> span = new ReadOnlySpan<byte>(a);
int idx = span.IndexOf(length + 1, 10, 99);
Assert.Equal(-1, idx);
}

public static void ZeroCountIndexOf_Byte()
{
int length = 100;
byte[] a = new byte[length];
a[50] = 99;
ReadOnlySpan<byte> span = new ReadOnlySpan<byte>(a);
int idx = span.IndexOf(0, 0, 99);
Assert.Equal(-1, idx);
}
}
}
81 changes: 81 additions & 0 deletions src/System.Memory/tests/Span/IndexOf.byte.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,86 @@ public static void MakeSureNoChecksGoOutOfRange_Byte()
Assert.Equal(-1, index);
}
}

[Fact]
public static void TestMatchWithIndexAndCount_Byte()
{
int length = 100;
byte[] a = new byte[length];
a[50] = 99;
Span<byte> span = new Span<byte>(a);

int idx = span.IndexOf(45, 10, 99);
Assert.Equal(50, idx);
}

[Fact]
public static void TestMatchWithIndexAndCountGreaterThanLength_Byte()
{
int length = 100;
byte[] a = new byte[length];
a[50] = 99;
Span<byte> span = new Span<byte>(a);

int idx = span.IndexOf(45, 75, 99);
Assert.Equal(50, idx);
}

[Fact]
public static void TestMatchWithCountGreaterThanLength_Byte()
{
int length = 100;
byte[] a = new byte[length];
a[50] = 99;
Span<byte> span = new Span<byte>(a);

int idx = span.IndexOf(0, 105, 99);
Assert.Equal(50, idx);
}

[Fact]
public static void TestNoMatchWithCountGreaterThanLength_Byte()
{
int length = 100;
byte[] a = new byte[length];
a[50] = 99;
Span<byte> span = new Span<byte>(a);

int idx = span.IndexOf(0, 105, 5);
Assert.Equal(-1, idx);
}

[Fact]
public static void TestNoMatchWithIndexAndCount_Byte()
{
int length = 100;
byte[] a = new byte[length];
a[50] = 99;
ReadOnlySpan<byte> span = new ReadOnlySpan<byte>(a);

int idx = span.IndexOf(45, 3, 99);
Assert.Equal(-1, idx);
}

[Fact]
public static void StartIndexTooLargeIndexOf_Byte()
{
int length = 100;
byte[] a = new byte[length];
a[50] = 99;
Span<byte> span = new Span<byte>(a);
int idx = span.IndexOf(length + 1, 10, 99);
Assert.Equal(-1, idx);
}

public static void ZeroCountIndexOf_Byte()
{
int length = 100;
byte[] a = new byte[length];
a[50] = 99;
Span<byte> span = new Span<byte>(a);
int idx = span.IndexOf(0, 0, 99);
Assert.Equal(-1, idx);
}
}
}