dotnet / dotnet/runtime

MemoryExtensions: multi-value IndexOfAny/IndexOfAnyExcept/LastIndexOfAny/LastIndexOfAnyExcept missing int/long fast paths

Open
#131,367 8 comments 0 reactions 0 assignees View on GitHub
area-System.Runtime help wanted
Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Description

Not sure if these were missed or intentionally left out. It refers to e.g.

https://github.com/dotnet/runtime/blob/8f65ed776323eac3ad84930f5c82a3f665280e70/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs#L938-L961

---

### Description

Several multi-value search methods in `MemoryExtensions` dispatch to vectorized `SpanHelpers` helpers for `byte` and `short`-sized types, but silently fall through to the slow scalar `IEquatable.Equals`-based path for `int` and `long`-sized types.

The `SpanHelpers` helpers already support all four sizes generically (`T : struct, INumber`) — the fix is purely in `MemoryExtensions.cs`.

The affected methods and their current coverage:

| Method | `byte` | `short` | `int` | `long` |
|---|:---:|:---:|:---:|:---:|
| `IndexOfAny(span, v0, v1)` | ✅ | ✅ | ❌ | ❌ |
| `IndexOfAny(span, v0, v1, comparer)` | ✅ | ✅ | ❌ | ❌ |
| `IndexOfAny(span, v0, v1, v2)` | ✅ | ✅ | ❌ | ❌ |
| `IndexOfAny(span, v0, v1, v2, comparer)` | ✅ | ✅ | ❌ | ❌ |
| `IndexOfAnyExcept(span, v0, v1)` | ✅ | ✅ | ❌ | ❌ |
| `IndexOfAnyExcept(span, v0, v1, comparer)` | ✅ | ✅ | ❌ | ❌ |
| `IndexOfAnyExcept(span, v0, v1, v2)` | ✅ | ✅ | ❌ | ❌ |
| `IndexOfAnyExcept(span, v0, v1, v2, comparer)` | ✅ | ✅ | ❌ | ❌ |
| `LastIndexOfAny(span, v0, v1)` | ✅ | ✅ | ❌ | ❌ |
| `LastIndexOfAny(span, v0, v1, comparer)` | ✅ | ✅ | ❌ | ❌ |
| `LastIndexOfAny(span, v0, v1, v2)` | ✅ | ✅ | ❌ | ❌ |
| `LastIndexOfAny(span, v0, v1, v2, comparer)` | ✅ | ✅ | ❌ | ❌ |
| `LastIndexOfAnyExcept(span, v0, v1)` | ✅ | ✅ | ❌ | ❌ |
| `LastIndexOfAnyExcept(span, v0, v1, comparer)` | ✅ | ✅ | ❌ | ❌ |
| `LastIndexOfAnyExcept(span, v0, v1, v2)` | ✅ | ✅ | ❌ | ❌ |
| `LastIndexOfAnyExcept(span, v0, v1, v2, comparer)` | ✅ | ✅ | ❌ | ❌ |

### Analysis

The `SpanHelpers` helpers (`IndexOfAnyValueType`, `IndexOfAnyExceptValueType`, `LastIndexOfAnyValueType`, `LastIndexOfAnyExceptValueType`) are all generic over `T : struct, INumber` and their implementations are fully vectorized for all four sizes (the `Debug.Assert(value0 is byte or short or int or long, ...)` inside confirms this). No changes to `SpanHelpers` are required.

The fix is to add `sizeof(int)` and `sizeof(long)` branches to each affected method in `MemoryExtensions.cs`, e.g. for `IndexOfAny(span, v0, v1)`:

```csharp
else if (sizeof(T) == sizeof(int))
{
return SpanHelpers.IndexOfAnyValueType(
ref Unsafe.As(ref MemoryMarshal.GetReference(span)),
Unsafe.BitCast(value0),
Unsafe.BitCast(value1),
span.Length);
}
else if (sizeof(T) == sizeof(long))
{
return SpanHelpers.IndexOfAnyValueType(
ref Unsafe.As(ref MemoryMarshal.GetReference(span)),
Unsafe.BitCast(value0),
Unsafe.BitCast(value1),
span.Length);
}

Contributor guide

Open the contributing guide

Research direction

Start in src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs around the linked overloads, and compare the affected IndexOfAny, IndexOfAnyExcept, LastIndexOfAny, and LastIndexOfAnyExcept methods with the existing byte and short branches. Use the corresponding generic SpanHelpers value-type helpers as the reference; done means all listed overloads dispatch int and long spans to those helpers without changing SpanHelpers.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
performance
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
78/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.