-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Forward Span-based MemoryExtensions overloads to ReadOnlySpan overloads #48669
Conversation
Tagging subscribers to 'size-reduction': @eerhardt, @SamMonoRT, @marek-safar, @tannergooding, @CoffeeFlux Issue DetailsAny reason not to do this? I'm not sure why it was done this way in the first place... seems like there must have been a reason?
|
I couldn't figure out the best area label to add to this PR. If you have write-permissions please help me learn by adding exactly one area label. |
Tagging subscribers to this area: @GrabYourPitchforks Issue DetailsAny reason not to do this? I'm not sure why it was done this way in the first place... seems like there must have been a reason?
|
I want to say it was a limitation of the JIT, where passing around complex structs ( |
Both these methods produce identical codegen in .NET 6. using BenchmarkDotNet.Attributes;
using System;
using System.Runtime.CompilerServices;
namespace ConsoleAppBenchmark
{
[DisassemblyDiagnoser]
public class SpanFwdRunner
{
private readonly int[] _ints = new int[] { 1, 2, 3 };
[Benchmark(Baseline = true)]
[Arguments(2)]
public bool Control(int value)
{
Span<int> span = _ints;
return span.Contains(value);
}
[Benchmark(Baseline = false)]
[Arguments(2)]
public bool Variable(int value)
{
Span<int> span = _ints;
return span.ContainsEx(value);
}
}
internal static class Extensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool ContainsEx<T>(this Span<T> span, T value) where T : IEquatable<T>
{
return ((ReadOnlySpan<T>)span).Contains(value);
}
}
} @AndyAyersMS Do you know if my theory about a previous JIT not optimizing this scenario correctly is viable? If something changed between earlier versions of .NET and vCurrent that means we can simplify this code, I'm all for Steve's proposed changes. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's wait for the JIT team to chime in, but LGTM!
One thing this change could risk is pushing some methods over the "512 max tracked locals" limit. I wrote a sample that demonstrates this: asm diff. While the codegen is the same (yay!), the number of locals used is considerably higher with the cast. C# codeprivate static bool ContainsSpan(int[] source, int a, int b, int c)
{
if (source.AsSpan().Contains(a))
{
return true;
}
if (source.AsSpan().Contains(b))
{
return true;
}
if (source.AsSpan().Contains(c))
{
return true;
}
return false;
}
private static bool ContainsReadOnlySpan(int[] source, int a, int b, int c)
{
if (((ReadOnlySpan<int>)source.AsSpan()).Contains(a))
{
return true;
}
if (((ReadOnlySpan<int>)source.AsSpan()).Contains(b))
{
return true;
}
if (((ReadOnlySpan<int>)source.AsSpan()).Contains(c))
{
return true;
}
return false;
} We have tooling that compares ASM between different Jit's. It could be interesting to leverage that for diffing the CoreLib (and/or the shared framework too) with & without this change. Unfortunately I do not have the time for doing it right now, but a note if someone else is interested: do not forget to eliminate |
Not sure. Perhaps on it was x64/SysV ABI, or arm64, where we might have been spilling the spans to stack and back? Or local copies of pass through structs blocking tail calls? Much of that should be mitigated by now. |
The extra forwarding layer will also regress the case where these methods are used over reference types (the generics inlining problem). |
Yep, the JIT budgets tend to get consumed very fast for code that works with Spans due to extensive use of aggressive inlining, so calling method may not actually be that large to hit this limit. |
I'm not sure how to evaluate the tradeoffs here. This is a fair amount of non-trivial code duplication; just from a maintenance perspective I'd like to get rid of it, but then also arguably unnecessary binary size (e.g. a default Blazor wasm app currently uses both the Span and ReadOnlySpan overloads of @jkotas, @AndyAyersMS, would you prefer to see this closed then? |
|
61c5c55
to
c6453ca
Compare
We may want to revaluate this limit. Last time we looked (~5 years ago) there were very few methods that came near. But perhaps things have changed.
I know David was poking around here a while back -- at some point we should try and fix this. |
Don't forget the examples in #11905 ;) |
Any reason not to do this? I'm not sure why it was done this way in the first place... seems like there must have been a reason?
cc: @GrabYourPitchforks