-
Notifications
You must be signed in to change notification settings - Fork 1k
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
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
Feature suggestion: zero-allocation "params" via Span<T> / ReadOnlySpan<T> #535
Comments
Span<T>
and params
possibility
Quick bit of exploration; at the moment the
(that is comparing Update: these numbers are out of date now; there's a better way - see |
Create a struct containing the necessary number of T fields on the stack and somehow (DangerousCreate?) build a span pointing to the first field of the struct. |
@mikedn that would definitely work, but I'm not sure that it is necessary to add the additional struct, when the raw data can also just be stackalloc'd - certainly a valid consideration, though |
For primitive types yes, you'd probably keep using stackalloc. The struct is only needed for reference types. Or who knows, maybe the struct would be also good for primitive types, it depends which construct is best for the JIT. Probably neither since both imply taking the address of a local :) |
Yeah, and in these cases you're really just stackalloc'ing the references, which are a fixed size (sizeof(IntPtr)). The struct would be an interesting take, but I'm not sure what it gains you. It is a cool idea, though. |
The GC needs to be able to track those references so they can't be in stackallocated memory, the GC doesn't track that. Anyway, all this sounds very tempting but it looks like the main problem is that the resulting IL code is unverifiable/unsafe/unanyhting. |
added benchmark for static-backed for the |
@mikedn once they're inside the span, they should be essentially |
Stackallocated space cannot be freed before the method returns. That is, if you do allocate in a loop you'll end up with stack overflow. That's actually one reason to stick to struct because the same local could be reused. At the risk of getting some funny side effects if the called method somehow manages to persist the span for later use.
Hmm, I doubt that. That would imply that GC understands span and scans it but AFAIK that's not the case. The way things work is that the GC scans the array/object the span points to. But in our case the span doesn't point to a GC object, it points to random memory so the GC can't do anything. Sure, random memory in this case is actually stack memory and the GC scans the stack but in a different manner. |
ValueTuple? |
@benaadams I was thinking that too. But you'd essentially need some kind of stand in type in the method signature to tell the compiler to make a ValueTuple of the right length. You would probably also want some struct based ValueTuple iterator for consumption on the receiving end.
|
@mattnischan it is simpler than that, I think - just something like (this syntax won't compile currently - is purely for indication):
This does assume that Testing with
gives:
which is really good |
I see what you mean. I was thinking I think that layout stuff gets tricky with |
Each tuple size is a completely different and incompatible type. You'd be forced to pass the tuple representing the maximum number of elements. |
@benaadams see |
@HaloFour you can cheat via Span - see the illustration above |
Ah, I see. Had to refresh the page. Perf does look good but the fact that you have to break out the |
But the type isn't really important. It can be any type that has a suitable number of reference type fields. The fields can even be |
@mgravell |
That seems like a safe change that could be made. Paging @jcouv |
@mikedn @cyrus yeah, the |
The other "fix" of course being: use stackalloc but do it so that it only allocates once per stack frame. |
ValueTuple is generic. "Auto" allows JIT to pack/pad/align fields as appropriate to their size to make accesses more efficient. |
I wonder how |
@VSadov kinda related and might simplify a lot of this... Thinking aloud here:
Or even:
I.e. wrap a lot of the ugly here up a level into the |
A struct at the callee side seems more suitable to hold the variables. Possibly compiler-generated one - like in Fixed. The idea of using Span as an indexer that is not coupled with the underlying storage and yet is itself a struct is interesting. Some care would be needed to not let such spans escaping their scope. There could be some challenges here. - What if callee returns the span or assigns to another ref/out parameter . . . |
Span<T> foo = stackalloc T[len]; is the current idea for safe stackallock'd spans. It did not get a lot of scrutiny though, so cannot be absolutely sure if it will work. Note that |
@VSadov in the case of params usage, the compiler is fully in charge of the scope, so it can't escape. For more general purposes, that's surely where your ref-like stack referring stuff comes in, no? |
So X calls a span params method Y and Y returns the span it got as a parameter. X now return the span returned from Y to its caller. Ooops, the caller of X now has a span that points into the former frame of X. |
@mikedn the call to Y would be marked as a stack-referring expression, so the result is stack-referring and thus cannot be used as a return or out/ref result; that is, I believe, part of the ref-like proposal that is a work in progress separate to this - basically: that's already considered separately, if i understand the ref-like type spec proposal; span-t is a ref-like type for this purpose. |
@mgravell I'm not familiar with that proposal but it sounds kind of fishy. So I call a span params method and that method allocates an array, makes a span out of it and returns it. Now the compiler thinks that the returned span refers to the stack and thus limits my ability to return the span? Oh well, I suppose span isn't too different from |
Slightly more updated version of the span safety proposal: It is easier to follow/comment via a PR: |
@VSadov hoorah! I guessed correctly on the declaration syntax ( |
Yep, as I suspected this stack-referring thing is rather infectious. It's cool and at the same time kind of scary, many C# developers will probably have trouble understanding all this wizardry. Sometimes I wonder what C# language turns into :) |
@mikedn I think of it very much like pointers: most c# devs barely know they are a thing in c#, but for certain areas they are heavily used. As long as the scenarios "most devs" are likely to see are clear and simple, the subtle depths don't worry me. Still better than pointers. |
When C# was born MS said it was closer to C++ than to Java :) |
I like this proposal. |
I think #179 cover this proposal (if we allow any type for params that is assignable from T[]), we just need to emit stackalloc where possible (as an optimization). |
#179 talks about IEnumerable-T, which is ref-type. It is absolutely
impossible to store a span or a stackalloc in a ref-type, so this is not
something that can added as simply an optimization - this is a
fundamentally different scenario.
Additionally, the key point of this suggestion is to not allocate - and by
definition an IEnumerable-T requires an allocation (unless it is a
constained-T variant).
So: I don't think the overlap is as big as it might appear.
…On 29 Dec 2017 7:46 pm, "Alireza Habibi" ***@***.***> wrote:
I think #179 <#179> cover this
proposal (if we allow any type for params that is assignable from T[]), we
just need to emit stackalloc where possible (as an optimization).
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#535 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AABDsDaKubEG8vIt0DrxFCtphG22-KCRks5tFUGJgaJpZM4NQZgf>
.
|
I was talking about semantics where these are all permitted, but I missed the fact that Span is a ref struct and doesn't implement any interfaces. so it should be a special case anyways. |
Presumably a compiler generated nested struct would be safer than relying on the internals of
|
I started working on stackalloc initializers (dotnet/roslyn#24249), now we should be able to reuse its lowering for params Span. For reference types we fall back to array though. Is there anyone from LDT interested to champion this proposal? |
I don't know if stackalloc initializers are worth doing with their performance cost relative to allocating an array ( A generated struct used for a Span pointer would be nice (relying on Tuple is suspect due to auto layout) but that has the same problem that #992, #1147 and https://github.com/dotnet/corefx/issues/26228 do, doesn't it? This is valid code (and almost certainly buggy): public unsafe Span<int> SpanGenerated()
{
var args = new Generated5Int(1, 2, 3, 4, 5);
return Execute(new Span<int>(Unsafe.AsPointer(ref args.I1), 5));
}
Span<int> Execute(/*params*/ Span<int> input) => input; Does stackalloc have that issue as well? Edit: what I mean is supporting |
stackalloc is fixed memory so it doesn't have the problem; however you can't return it from the method as that stack space is then invalid; as it gets unwound - though you can call into other methods with it (as its still valid going deeper into the stack) |
Not sure if I understand correctly, do you mean runtime helpers that would allow block init? I guess "other array like params things" refers to #179? and what it has to do with those methods? |
Sorry I was just saying #179 should be unrelated to this even if #179 were to support As I am seeing it, safe code |
This is how I imagine we can make this work: Currently array initializers are lowered to a static field of a struct with a size of all elements combined, iff all values are constants (plus a few other conditions), for example,
stackalloc initializers can take advantage of the same mechanism but instead of
Finally,
|
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
This is broadly related to #178 and #179
C# has the
params
keyword that allows convenient varadic signatures, for example:Here, the signature is
string[] string.Split(params char[] separator)
.This usage is awkward because the compiler emits a
new char[]
every time it is invoked; this might be GEN-0 collected if you get lucky, but it still has overhead.The advent of
Span<T>
offers new possibilities. For example, what if the following signature was possible:Then:
Could be compiled as the equivalent of:
Since this is a compiler generated implementation, it should bypass project-level
unsafe
restrictionsand use the
Span<T>
type guarantees to ensure stack-referring safety, etc.This makes it possible to have zero-allocation
params
varadic usage. Obviously the usual "last parameter" etc rules would apply.If
unsafe
is not available (because of iterator blocks, async, etc), it can be implemented as a span over an array instead, just like regularparams
might have emitted:although actually, I'm struggling to think of a scenario involving primitives where this would be needed, even in an iterator block etc, since the call-site is so localized; it might be possible to avoid this entirely. If the target method can't use spans because of async etc, then the target method won't have span in the signature, so: bullet dodged. Note that if this array version is required, and if the values are constant / literals and the target is
ReadOnlySpan<T>
(notSpan<T>
), then this array could be cached as astatic
:Thoughts....?
Additional idea for extending pre-existing APIs: if (haven't thought it through) it is possible to have two
params
overloads forparams Foo[]
andparams Span<Foo>
against the same target, then theparams Span<Foo>
should take precedence if the caller is using compiler-providedparams
support, since it can be called in a lower overhead way. Meaning, given:then:
would call the second method. However, if the caller is more explicit with an
int[]
local / expression, then the first method is called.Additional unknown: what to do if the
T
is not one that can normally bestackalloc
'd (so: not a known primitive), for examplestring
? Just use the array version? or use something more subtle like:The text was updated successfully, but these errors were encountered: