Skip to content
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

Yet another take on class stack allocation #3166

Closed
En3Tho opened this issue Feb 6, 2020 · 14 comments
Closed

Yet another take on class stack allocation #3166

En3Tho opened this issue Feb 6, 2020 · 14 comments

Comments

@En3Tho
Copy link

En3Tho commented Feb 6, 2020

I don't know if it was already proposed or not, but here is my take on this one:

I wonder if we can do a more simple version of class stackalloc via already defined C# language keywords?

My proposed rules are :
We can have a class hierarchy but interfaces are forbidden on all levels of hierarchy
Deriving from non ref classes is forbidden
Classes for stackalloc should be either abstract or concrete sealed
Abstract class (every class in hierarchy to be precise) for stackalloc should always have "ref" keyword
Class for stackalloc should behave the same way as ref struct so rules are compiler enforced
Concrete class for stackalloc should always be sealed for devirtualization (I can be wrong here but I've heard there was a pr for jit to introduce this kind of thing)
Concrete class for stackalloc should have "ref" keyword along with "sealed" to match ref struct behavior and previous requirements
No stackallocked arrays of ref classes, but we basically already have this kind of thing as c# doesn't let us do so
Can't be static (well, quite obviously)

This way we can introduce stackallocked internal or maybe even public helpers with hierarchy and polymorphism but at same time safe to stack allocate, and without any kind of language changes while utilizing already existing behavior

As an example:

public abstract ref class SomeClass
    {
        public SomeOtherRefClass Ref { get; set; } // behaves like a ref struct so is a direct part of this class memory, can be only a concrete sealed ref class
        // i guess we can have a generic field T of "ref class, SomeOtherRefClass" to support any implementation of SomeOtherClass
        // but it still does has to be concrete
        // on the other hand if we can hold a stack reference of ref class then maybe we shouldn't care about that
        // and we actually can hold any implementation of ref class base here
        public SomeOtherNonRefClass { get; set; } // simple reference
        public abstract void Do();
        public virtual void MaybeDo() { }
    }

    public sealed ref class StackAllockedClass : SomeClass
    {
        public override void Do() { }
    }

    public sealed ref class StackAllockedClassWithVirtualOverride : SomeClass
    {
        public override void Do() { }
        public override void MaybeDo() { };
    }

    public static class SomeClassUser
    {
        public static void Use(SomeClass someClass) { } // can be any child of SomeClass
    }

My first design was to allow deriving from non ref class but we can't be sure if a method taking in a non ref base won't be using it in a lambda or whatever. Is it even possible to check if class is heap or stack allocated in this case? Basically it's like a ref struct with inheritance.

Anyway, would like to hear your thoughts on this one.

@Unknown6656
Copy link
Contributor

Maybe I'm missing something ..... but why should one want to stack-alloc classes? The whole point of C# was kinda to get rid of allocation, memory management, etc.?

Furthermore, how do you want to handle classes which grow/shrink in size during runtime, e.g. Lists etc.? Do you move them (in parts) to the heap?

Why is your aim currently not accomplishable by using (ref) structs? Do you want inheritance with your stack allocation?


I suspect that your proposal requires CLR-changes. I cannot see it being rewarding enough for all the effort involved.....

@theunrepentantgeek
Copy link

@Unknown6656 - the fact that C# allows you to mostly ignore memory issues doesn't mean that allocation isn't going on under the hood. For some hot-path scenarios, reducing the number of allocations (and thus increasing the length of time between garbage collections) can be really useful.

If you dig into how a List<T> works, there's a statically sized header and an array buffer (I think it's called _items) that stores the actual items. Given that the size of the buffer isn't known until runtime, it would have to be heap allocated - but the header could theoretically be allocated on the stack.

@theunrepentantgeek
Copy link

FWIW, there's been work done on the runtime to try out automatic stack allocation - avoiding heap use when an instance can be definitively shown as not escaping the current method. (I believe they've shown the idea has significant promise, but that analysis of whether an instance escapes or not is more difficult than anticipated.)

I believe this is by far the best approach as it would benefit all code, not just those few cases where someone manually annotates the code. We don't need C# syntax for this.

@theunrepentantgeek
Copy link

Further reading ...

Roslyn Issue#16154 - Allow stackalloc for reference types when they are used as locals when not escaping

Runtime Issue #4584 - CLR/JIT should optimize "alloc temporary small object" to "alloc on stack" automatically

Core CLR PR#6653 - Work towards objects stack allocation

Core CLR PR#20251 - Document describing upcoming object stack allocation work

@En3Tho
Copy link
Author

En3Tho commented Feb 9, 2020

@theunrepentantgeek @Unknown6656 Yes, I've actually read all of these. But I guess my proposal (even if it may seem quite restricted) was more about specific cases when developer can explicitrly allocate class on stack to reduce memory pressure on gc or simply make little things go faster.
Even with escape analysis (which is very difficult thing to do) we can't be always sure whether our object is heap or stack allocated. So the idea was to try and think about how can we implement explicit object stack allocation without having to invent the wheel (like new keywords and stuff) or resort to unsafe. It's very restricted tho, but at the same time all those rules are what can it work.

@AartBluestoke
Copy link
Contributor

" It's very restricted tho" -- probably more restricted than you realize; the number of ways an object can potentially escape and have a reference stored on the stack is huge: eg: is string.Join(",",this.myList); safe (or any other lambda)?
is this.ToString() safe?

even in your example, whats to stop the following code:

public static class SomeClassUser
{
ListthingCache = new List();
public static void Use(SomeClass someClass) {thingCache.Add(someClass); } // can be any child of SomeClass
}

@En3Tho
Copy link
Author

En3Tho commented Feb 10, 2020

@AartBluestoke Hey Andrew. Ref struct like behavior (it's in the rules) won't let this call happen as someclass is a ref class. And at the same time there is a rule of no stackallocked arrays. So combined it basically means no arrays at all. As I've said, it's very restricted and globally people want something hugely different. They want "real" classes to be stackallocked. And in fact that's why all these restrictions come into play because as you said it's just way too hard to analyze. Well, SpanToString is safe isn't it?

@HaloFour
Copy link
Contributor

Or, you could use a struct? It's already very easy to get a struct onto the heap.

I don't see the point in having a completely different kind of type that is incredibly restricted for the only benefit of being able to allocate it on the stack.

@En3Tho
Copy link
Author

En3Tho commented Feb 11, 2020

@HaloFour Structs are even more restricted than this kind of thing anyway. I can be wrong but imo such ref classes could easily replace like third of a code base leaving collections, entities, closures and stuff like that. Things like builders, helpers, anything dealing with single thread execution. Well obviously there are other proposals but I think they are all very similar to this one - as soon as you mark your class "stackonly" you automatically get all these restrictions or else it simply becomes impossible to fully analyze whether you class can get heap allocated or not. I guess what I wanted to propose is something that's familiar already but can be useful.

@birbilis
Copy link

Aren't they adding records in newer C#?

@HaloFour
Copy link
Contributor

@birbilis As of now records are only going to be reference types.

@En3Tho
Copy link
Author

En3Tho commented Oct 18, 2020

Going to close this one as it's "yet another..." anyway. Maybe the real thing we (or me in particular) need is Shapes...? They will make struct based programming much more appealing. Also, I've read there are lots of different ideas about how jit can optimize and enregistrate/inline structs so I guess the way to go is to wait for .Net 6 and C# 10 :)

@En3Tho En3Tho closed this as completed Oct 18, 2020
@birbilis
Copy link

Going to close this one as it's "yet another..." anyway. Maybe the real thing we (or me in particular) need is Shapes...? They will make struct based programming much more appealing. Also, I've read there are lots of different ideas about how jit can optimize and enregistrate/inline structs so I guess the way to go is to wait for .Net 6 and C# 10 :)

just my 1c on the term "Shape", if you mean the same thing as described here: #164 (comment)

@En3Tho
Copy link
Author

En3Tho commented Oct 19, 2020

Yep, it's them. That's why I specially called them Shapes and not TypeClasses

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants