MemoryExtensions: multi-value IndexOfAny/IndexOfAnyExcept/LastIndexOfAny/LastIndexOfAnyExcept missing int/long fast paths
- 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
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