Replies: 52 comments
-
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 |
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
@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 |
Beta Was this translation helpful? Give feedback.
-
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 :) |
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
added benchmark for static-backed for the |
Beta Was this translation helpful? Give feedback.
-
@mikedn once they're inside the span, they should be essentially |
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
ValueTuple? |
Beta Was this translation helpful? Give feedback.
-
@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.
|
Beta Was this translation helpful? Give feedback.
-
@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 |
Beta Was this translation helpful? Give feedback.
-
I see what you mean. I was thinking I think that layout stuff gets tricky with |
Beta Was this translation helpful? Give feedback.
-
Each tuple size is a completely different and incompatible type. You'd be forced to pass the tuple representing the maximum number of elements. |
Beta Was this translation helpful? Give feedback.
-
@benaadams see |
Beta Was this translation helpful? Give feedback.
-
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 :) |
Beta Was this translation helpful? Give feedback.
-
@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. |
Beta Was this translation helpful? Give feedback.
-
When C# was born MS said it was closer to C++ than to Java :) |
Beta Was this translation helpful? Give feedback.
-
I like this proposal. |
Beta Was this translation helpful? Give feedback.
-
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). |
Beta Was this translation helpful? Give feedback.
-
#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>
.
|
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
Presumably a compiler generated nested struct would be safer than relying on the internals of
|
Beta Was this translation helpful? Give feedback.
-
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? |
Beta Was this translation helpful? Give feedback.
-
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 |
Beta Was this translation helpful? Give feedback.
-
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) |
Beta Was this translation helpful? Give feedback.
-
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? |
Beta Was this translation helpful? Give feedback.
-
Sorry I was just saying #179 should be unrelated to this even if #179 were to support As I am seeing it, safe code |
Beta Was this translation helpful? Give feedback.
-
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,
|
Beta Was this translation helpful? Give feedback.
-
Converting this issue to a discussion. The tracked champion for |
Beta Was this translation helpful? Give feedback.
-
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:Beta Was this translation helpful? Give feedback.
All reactions