Skip to content

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

Description

@Rob-Hague

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

public static unsafe int IndexOfAnyExcept<T>(this ReadOnlySpan<T> span, T value0, T value1) where T : IEquatable<T>?
{
if (RuntimeHelpers.IsBitwiseEquatable<T>())
{
if (sizeof(T) == sizeof(byte))
{
return SpanHelpers.IndexOfAnyExceptValueType(
ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(span)),
Unsafe.BitCast<T, byte>(value0),
Unsafe.BitCast<T, byte>(value1),
span.Length);
}
else if (sizeof(T) == sizeof(short))
{
return SpanHelpers.IndexOfAnyExceptValueType(
ref Unsafe.As<T, short>(ref MemoryMarshal.GetReference(span)),
Unsafe.BitCast<T, short>(value0),
Unsafe.BitCast<T, short>(value1),
span.Length);
}
}
return SpanHelpers.IndexOfAnyExcept(ref MemoryMarshal.GetReference(span), value0, value1, span.Length);
}


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<T>.Equals-based path for int and long-sized types.

The SpanHelpers helpers already support all four sizes generically (T : struct, INumber<T>) — 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<T> 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):

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions