-
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
Proposal: Stack-allocated closures #1862
Comments
LINQ queries are lazily evaluated - all of the lambda's used with LINQ are stashed away in member variables and only called if/when the enumerable requires. Even if the expression was immediately evaluated with a |
Yes, but they could be still evaluated in scope of current method, as in my example (or when ended with Those can be done as something returning |
But they also might not be. In general the lambdas are assigned to fields on allocated iterator classes which are on the heap. This immediately violates the safety rules required for |
Valid points. I will try to rephrase the proposal and examples not to use any LINQ. |
It is unclear to me what language change htis is proposing. This appears to be more about introducing a new API and having the compiler optimize in the presence of it. Do you think there would be any actual change to the language here? |
I think converting lambdas to a type like |
How exactly would closures work with your design? If closures were |
I've updated the initial post, removed all LINQ-related stuff. @CyrusNajmabadi, I suggest adding keyword to the language, that clearly marks the intent of creating @svick, is unsafe out of discussion? I suggest using IntPtr for capturing the closure. Address of stack variable is always pinned anyway. Some pseudo-compiler-generated code:
How would it look with my proposal:
Does this make sense? |
Could you clarify that? C# already knows how to convert lambdas to arbitrary delegates. Would there need to be a change here? Thanks! |
Yes, but in C#, delegate is always reference type (inherits from |
I think it would be very confusing if |
Related: Proposal: Struct Lambdas #1060 |
Nothing about inheriting from a class necessitates something being a reference type. For example, everything inherits from System.Object. All structs inherit from System.ValueType (which itself is a reference type). So we could certainly have a struct delegate type that derived from System.Delegate. I don't think the language would care at all :)
In what way would it be incompatible? |
@CyrusNajmabadi good point. I considered the incompatibility as in "you can't upcast from ValueFunc to Delegate, or use Delegate.Combine on them" (since it would involve boxing), I did not realize that you can't upcast ref struct to ValueType. So the language can stay as-it-is, and implement this only on optimization level. |
Does it need special syntax at the site of the lambda expression at all? Lambas require a target type anyway, so we could just make the lambda compile to a One could argue that the Agreed that this isn't applicable to LINQ, but IMO there are many application where it would be useful, such as LINQ-like functions over materialized containers (arrays, lists, |
Addendum: You can achieve almost the same thing today, like this (Sharplab):
... but it's annoyingly verbose. So maybe it would be easier if we could just get some syntactic sugar for that? |
It seems like Roslyn could automatically do this optimization. The only thing that might benefit is an attribute for method parameters that would indicate if the argument is stored for use after a method executes. |
Fyi, work has started on stack-allocated objects. dotnet/coreclr#20251 |
Roslyn optimizations are cool, but such things are always a thin ice. What provides guarantees is types. I'd propose that:
In such setup readonly ref struct UnreadableScrewedName<>xy : ValueFunc<int, int>
{
public int y;
public UnreadableScrewedName<>xy()
: base(UnmanagedPointerCompilerMagicGetter(Invoke)) { }
int Invoke(int x) => x + y;
}
...
var closure<> = new UnreadableScrewedName<>xy();
closure<>.y = 1;
G(F(in closure<>)); Lambda expression syntax stays intact as lambdas already can specify to Func<..> or Expression<Func<..>> (as DanielSchuessler has noticed), so why whould a mechanism for ValueFunc<..> differ? One of the drawbacks I see at the moment is that all those GetType and ToString methods can't behave as virtual, i.e. once you upcast from |
Slightly OT: Variations of all the "what things can safely be left on the stack to make an operation non-allocating" set of discussions seems to be the primary place this occurs, and it often gets bogged down in escape analysis (eg: we can't do the above for lambdas because we can't force a complaint implementation). This is especially true when many of the things we take as part of the language itself are merely an implementation detail (eg: async x.ConfigureAwait(false) ). |
Take a look at #2482 which investigates what it would take to make linq allocation free. ref struct closures are definitely a part of the story, but a lot more is needed as well :-) |
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
Problem
There are lot of places in code, where anonymous delegates (lambdas) passed to method are immediatelly evaluated, not stored, and when method returns, no longer used. This goes, for example, to
ConcurrentDictionary.AddOrUpdate()
calls.However, such delegates and their closures are always allocated on heap.
Example:
Solution
Stack type delegates
Method accepting callback can declare it's intention not to store the callback with having
ref System.ValueFunc<>
/ref System.ValueAction<>
parameter (similar toSystem.Threading.Tasks.ValueTask<>
. That one will beref readonly struct
, so it can be kept on stack only, and also does not support multicasting.When constructing the delegate from lambda, there is need to define that the lambda is actually value function. Proposal is to use
ref
keyword before the definition.Stack only closure
When compiler is building closure, it can enumerate all it's call-sites, and whenever every single call-site is referenced by
ValueFunc<>
(or alternative), it generates the closure asref struct
.Rationale
The performance benefit is not game-changing, but reducing GC stress would be measurable.
The text was updated successfully, but these errors were encountered: