Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 2bff491

Browse files
authored
Port SequentialEqual() optimizations to ReadOnlySpan overloads (#28073)
1 parent 2de3a3c commit 2bff491

File tree

3 files changed

+129
-2
lines changed

3 files changed

+129
-2
lines changed

src/Common/src/CoreLib/System/MemoryExtensions.cs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -635,12 +635,34 @@ public static bool SequenceEqual<T>(this ReadOnlySpan<T> first, ReadOnlySpan<T>
635635
where T : IEquatable<T>
636636
{
637637
int length = first.Length;
638-
if (typeof(T) == typeof(byte))
638+
if (typeof(T) == typeof(byte) || typeof(T) == typeof(sbyte))
639639
return length == second.Length &&
640640
SpanHelpers.SequenceEqual(
641641
ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(first)),
642642
ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(second)),
643643
length);
644+
645+
if (typeof(T) == typeof(char) || typeof(T) == typeof(short) || typeof(T) == typeof(ushort))
646+
return length == second.Length &&
647+
SpanHelpers.SequenceEqualBytes(
648+
ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(first)),
649+
ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(second)),
650+
((nuint)length) * 2); // If this multiplication overflows, the Span we got overflows the entire address range. There's no happy outcome for this api in such a case so we choose not to take the overhead of checking.
651+
652+
if (typeof(T) == typeof(int) || typeof(T) == typeof(uint))
653+
return length == second.Length &&
654+
SpanHelpers.SequenceEqualBytes(
655+
ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(first)),
656+
ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(second)),
657+
((nuint)length) * 4); // If this multiplication overflows, the Span we got overflows the entire address range. There's no happy outcome for this api in such a case so we choose not to take the overhead of checking.
658+
659+
if (typeof(T) == typeof(long) || typeof(T) == typeof(ulong))
660+
return length == second.Length &&
661+
SpanHelpers.SequenceEqualBytes(
662+
ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(first)),
663+
ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(second)),
664+
((nuint)length) * 8); // If this multiplication overflows, the Span we got overflows the entire address range. There's no happy outcome for this api in such a case so we choose not to take the overhead of checking.
665+
644666
return length == second.Length && SpanHelpers.SequenceEqual(ref MemoryMarshal.GetReference(first), ref MemoryMarshal.GetReference(second), length);
645667
}
646668

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using Xunit;
6+
7+
namespace System.SpanTests
8+
{
9+
public static partial class ReadOnlySpanTests
10+
{
11+
[Fact]
12+
public static void ZeroLengthSequenceEqual_Long()
13+
{
14+
long[] a = new long[3];
15+
16+
ReadOnlySpan<long> first = new ReadOnlySpan<long>(a, 1, 0);
17+
ReadOnlySpan<long> second = new ReadOnlySpan<long>(a, 2, 0);
18+
bool b = first.SequenceEqual<long>(second);
19+
Assert.True(b);
20+
}
21+
22+
[Fact]
23+
public static void SameSpanSequenceEqual_Long()
24+
{
25+
long[] a = { 488238291, 52498989823, 619890289890 };
26+
ReadOnlySpan<long> span = new ReadOnlySpan<long>(a);
27+
bool b = span.SequenceEqual<long>(span);
28+
Assert.True(b);
29+
}
30+
31+
[Fact]
32+
public static void SequenceEqualArrayImplicit_Long()
33+
{
34+
long[] a = { 488238291, 52498989823, 619890289890 };
35+
ReadOnlySpan<long> first = new ReadOnlySpan<long>(a, 0, 3);
36+
bool b = first.SequenceEqual<long>(a);
37+
Assert.True(b);
38+
}
39+
40+
[Fact]
41+
public static void SequenceEqualArraySegmentImplicit_Long()
42+
{
43+
long[] src = { 1989089123, 234523454235, 3123213231 };
44+
long[] dst = { 5, 1989089123, 234523454235, 3123213231, 10 };
45+
ArraySegment<long> segment = new ArraySegment<long>(dst, 1, 3);
46+
47+
ReadOnlySpan<long> first = new ReadOnlySpan<long>(src, 0, 3);
48+
bool b = first.SequenceEqual<long>(segment);
49+
Assert.True(b);
50+
}
51+
52+
[Fact]
53+
public static void LengthMismatchSequenceEqual_Long()
54+
{
55+
long[] a = { 488238291, 52498989823, 619890289890 };
56+
ReadOnlySpan<long> first = new ReadOnlySpan<long>(a, 0, 3);
57+
ReadOnlySpan<long> second = new ReadOnlySpan<long>(a, 0, 2);
58+
bool b = first.SequenceEqual<long>(second);
59+
Assert.False(b);
60+
}
61+
62+
[Fact]
63+
public static void SequenceEqualNoMatch_Long()
64+
{
65+
for (int length = 1; length < 32; length++)
66+
{
67+
for (int mismatchIndex = 0; mismatchIndex < length; mismatchIndex++)
68+
{
69+
long[] first = new long[length];
70+
long[] second = new long[length];
71+
for (int i = 0; i < length; i++)
72+
{
73+
first[i] = second[i] = (byte)(i + 1);
74+
}
75+
76+
second[mismatchIndex] = (byte)(second[mismatchIndex] + 1);
77+
78+
ReadOnlySpan<long> firstSpan = new ReadOnlySpan<long>(first);
79+
ReadOnlySpan<long> secondSpan = new ReadOnlySpan<long>(second);
80+
bool b = firstSpan.SequenceEqual<long>(secondSpan);
81+
Assert.False(b);
82+
}
83+
}
84+
}
85+
86+
[Fact]
87+
public static void MakeSureNoSequenceEqualChecksGoOutOfRange_Long()
88+
{
89+
for (int length = 0; length < 100; length++)
90+
{
91+
long[] first = new long[length + 2];
92+
first[0] = 99;
93+
first[length + 1] = 99;
94+
long[] second = new long[length + 2];
95+
second[0] = 100;
96+
second[length + 1] = 100;
97+
ReadOnlySpan<long> span1 = new ReadOnlySpan<long>(first, 1, length);
98+
ReadOnlySpan<long> span2 = new ReadOnlySpan<long>(second, 1, length);
99+
bool b = span1.SequenceEqual<long>(span2);
100+
Assert.True(b);
101+
}
102+
}
103+
}
104+
}

src/System.Memory/tests/System.Memory.Tests.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@
122122
<Compile Include="ReadOnlySpan\SequenceEqual.T.cs" />
123123
<Compile Include="ReadOnlySpan\SequenceEqual.byte.cs" />
124124
<Compile Include="ReadOnlySpan\SequenceEqual.char.cs" />
125+
<Compile Include="ReadOnlySpan\SequenceEqual.long.cs" />
125126
<Compile Include="ReadOnlySpan\Slice.cs" />
126127
<Compile Include="ReadOnlySpan\StartsWith.byte.cs" />
127128
<Compile Include="ReadOnlySpan\StartsWith.char.cs" />
@@ -229,4 +230,4 @@
229230
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
230231
</ItemGroup>
231232
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
232-
</Project>
233+
</Project>

0 commit comments

Comments
 (0)