Live data from Hacker News

Allocation is cheap in .NET until it is not

tooslowexception.com

21–30 of 67 posts

Re: Allocation is cheap in .NET until it is not

#21
post #2

This article demonstrates why generational GC with a bump-allocating nursery is so important. Without a semispace copying collector (which is usually impractical without a generational GC) you can't have bump allocation at all . Not having that fast path is a huge performance loss, as this article demonstrates.

I didn't understand a bit of that

The GC equivalent of stack allocation of temporary objects, but without having to be 100% accurate about object lifetimes.

It works even when the object is usually thrown away but occasionally isn’t.

Re: Allocation is cheap in .NET until it is not

#22
post #2

This article demonstrates why generational GC with a bump-allocating nursery is so important. Without a semispace copying collector (which is usually impractical without a generational GC) you can't have bump allocation at all . Not having that fast path is a huge performance loss, as this article demonstrates.

Mark-sweep-compact is another GC algorithm that supports bump allocation, and doesn't have the 2x overhead of semispace.

The point is that you need moving GC for bump-allocation to be possible. Traditional mark-and-sweep is non-moving while semispace collector is the simplest to describe moving GC. The practical takeaway from all that is that usual generational GC constructions with semispace minor collections and mark-and-sweep/compact major collections are in the complexity/efficiency sweet spot.

By the way it is possible and trivial, although somewhat non obvious, to construct non-moving generational GC. The general idea is that for simple M&S you have some list of live objects which you replace that with multiple per-generation lists (Claus Gittinger mentions this in his VM design talks, which probably means that at some point such GC was used by ST/X).

Re: Allocation is cheap in .NET until it is not

#23
post #12
post #9

Is there work ongoing or planned to try to add/improve escape analysis, as the article suggests?

In general - yes: https://github.com/dotnet/coreclr/issues/1784 . However, nothing more specific I can say...

I realize it's a pretty hard problem - and hadn't java already demonstrated the feasibility of it, I would have doubted it to be possible at all without major surgery to both language and runtime (special scoped types etc). So I guess my question is: is there something about C# or .NET that makes it much harder to do escape analysis than it is in Java world?

An evil example is

    class Something
    {  
       private static readonly Something _inst;
       public Something()
       {
          _inst = this;
       }
    }
Where the reference leaked just by instantiating it. Does java detect that this escaped the stack? How?

Re: Allocation is cheap in .NET until it is not

#24
post #12

Earlier quoted context omitted.

In general - yes: https://github.com/dotnet/coreclr/issues/1784 . However, nothing more specific I can say...

I realize it's a pretty hard problem - and hadn't java already demonstrated the feasibility of it, I would have doubted it to be possible at all without major surgery to both language and runtime (special scoped types etc). So I guess my question is: is there something about C# or .NET that makes it much harder to do escape analysis than it is in Java world? An evil example is class Something { private static readonl…

I believe it is not difficulty level problem but the fact that due to value types existence in .NET it was ok to live without escape analysis for so many years (like @cpeterso also pointed out).

Re: Allocation is cheap in .NET until it is not

#25
post #12

Earlier quoted context omitted.

In general - yes: https://github.com/dotnet/coreclr/issues/1784 . However, nothing more specific I can say...

I realize it's a pretty hard problem - and hadn't java already demonstrated the feasibility of it, I would have doubted it to be possible at all without major surgery to both language and runtime (special scoped types etc). So I guess my question is: is there something about C# or .NET that makes it much harder to do escape analysis than it is in Java world? An evil example is class Something { private static readonl…

> Does java detect that this escaped the stack? How?

There isn't one Java, rather lots of it.

So, in what concerns Oracle, there are reports from Oracle Labs that Graal is better than Hotspot on escape analysis.

No idea where IBM, Azul, Aicas, microEJ, PTC stand.

Even less regarding Android, because not only Google has done their own thing, each OEM also has the freedom to change how ART works.

Regarding your actual question, I have this feeling none of them is able to detect it.

Re: Allocation is cheap in .NET until it is not

#26
post #12

Earlier quoted context omitted.

In general - yes: https://github.com/dotnet/coreclr/issues/1784 . However, nothing more specific I can say...

I realize it's a pretty hard problem - and hadn't java already demonstrated the feasibility of it, I would have doubted it to be possible at all without major surgery to both language and runtime (special scoped types etc). So I guess my question is: is there something about C# or .NET that makes it much harder to do escape analysis than it is in Java world? An evil example is class Something { private static readonl…

Construction is a two step process. The space for something is allocated and then the constructor is called so there isn’t anything magical in that example.

It does however also remove half the guarantees about uninitiated object visibility in the java memory model because your leaking the reference inside the constructor.

Re: Allocation is cheap in .NET until it is not

#27
post #12

Earlier quoted context omitted.

In general - yes: https://github.com/dotnet/coreclr/issues/1784 . However, nothing more specific I can say...

For a VM as mature as .NET, I am surprised they don't do escape analysis. Java and Go both do escape analysis, though the coreclr issue you linked to makes the point that Java really needs escape analysis because it doesn't have value types like C# structs.

I'd say .NET is surprisingly immature considering it's age and it's popularity, at least compared to JVM and JS options.

Don't get me wrong I think .NET is great, and they've made some smart decisions that allow it to be competitive with much less engineering work on the VM.

As well as not having escape analysis it also lacks tiered compilation which is a much more pressing problem from my perspective, and is being actively tackled now.

Re: Allocation is cheap in .NET until it is not

#28
post #12

Earlier quoted context omitted.

In general - yes: https://github.com/dotnet/coreclr/issues/1784 . However, nothing more specific I can say...

I realize it's a pretty hard problem - and hadn't java already demonstrated the feasibility of it, I would have doubted it to be possible at all without major surgery to both language and runtime (special scoped types etc). So I guess my question is: is there something about C# or .NET that makes it much harder to do escape analysis than it is in Java world? An evil example is class Something { private static readonl…

This post has a nice investigation into 'Escape Analysis' in Java, https://shipilev.net/jvm-anatomy-park/18-scalar-replacement/

Shows that the Hotspot doesn't handle it in all scenarios:

> But, EA is not ideal: if we cannot statically determine the object is not escaping, we have to assume it does. Complicated control flow may bail earlier. Calling non-inlined — and thus opaque for current analysis — instance method bails. Doing some things that rely on object identity bail, although trivial things like reference comparison with non-escaping objects gets folded efficiently.

> This is not an ideal optimization, but when it works, it works magnificently well. Further improvements in compiler technology might widen the number of cases where EA works well.

Re: Allocation is cheap in .NET until it is not

#29
post #22

Earlier quoted context omitted.

Mark-sweep-compact is another GC algorithm that supports bump allocation, and doesn't have the 2x overhead of semispace.

The point is that you need moving GC for bump-allocation to be possible. Traditional mark-and-sweep is non-moving while semispace collector is the simplest to describe moving GC. The practical takeaway from all that is that usual generational GC constructions with semispace minor collections and mark-and-sweep/compact major collections are in the complexity/efficiency sweet spot. By the way it is possible and trivial…

Moving collectors get you best allocation throughput but impose other costs, which are hard to measure because they are design constraints. Obviously you cannot have a moving conservative collector so you must have stack maps, safe points, etc.

Or interactions with native code. How can native code hold a reference to a potentially movable object? .NET allows pinned pointers (obviously hurting compaction efficiency) while JNI uses double-dereferenced handles, of which there's a limit (65k in Android!) Compare to, say, JavaScriptCore, which uses a non-moving collector, and simply conservatively scans native stacks.

Whether these costs are important depends on your use case, but it's important to remember that we're rarely building isolated systems.

> non-moving generational GC

Yes, that's what Apple built for its ill-fated experiment with GC! Amusingly Apple also built its inverse: a moving manual memory manager. Google MoreMasters for some retro fun!

Post reply on HN