Allocation is cheap in .NET until it is not
tooslowexception.com
tooslowexception.com
Dev manager of Exchange used that line in a talk. Never were more insightful words spoken. Devs will move from C++ where they obsess about every allocation to .NET and they'll totally forget that allocation is expensive no matter what the platform or runtime.
Well, it's easier to do in a managed language. When you literally don't have to agonize or obsess over every allocation because you aren't responsible for cleaning it up (unmanaged held resources withstanding), you tend not to do so.
P.S.: You're always free to drop down into C or C++ if you want to get some speed, but of course you need to clean up after yourself there. A friend of mine wrote a good guide on doing so, if anyone cares https://github.com/TheBlackCentipede/PlatformInvocationIndep...
Wouldn't C# with structs and pointers do the job in many cases? I've been able to get 50-fold increases in speed through heavy optimizations, without switching to another language. Using C or C++ solely for a "speed boost" over C# is not only unnecessary, but it creates more problems than it solves. If you don't know how to optimize within C# (as a C# developer), how are you going to succeed in writing efficient C++ code?
Once you learn the nuances and limitations of making optimizations in C#, then you should start looking into how and when other languages such as C can wisely be used. To name an example, C makes it easier to micromanage assembly instructions (can be done in C# too, but not in a very practical way, and yes I mean assembly and not IL). C also contains more syntax and features which are suitable for bitwise micromanagement, whereas with C# it can be more awkward.
I think in general it is a culture problem.
Those of us that embraced managed languages, including for systems programming (Oberon, D, ...), know that we can be productive 99% of the time and just have to care how to do speed boost tricks on that 1% using profiler and low level language tricks.
In C and C++ communities there is a sub-culture of thinking too much ahead of time how much each line of code costs, thus speeding too much time with design decisions that actually have zero value in the context of the application being delivered.
The problem is not taking those decisions, rather taking them without validating if they are right with a profiler, or regard to the goals that have to be met for the application.
Beyond which any low level fine tuning, while fun, is needless engineering.
- structs
- unsafe code
- stack allocation in unsafe code (think alloca())
- attribute annotations for packing and inline calls across assemblies
- ref parameters
- ref returns
- readonly ref parameters
- Span<> and Memory<>
- Native memory allocation via MarshalInterop, SafeHandles
- Buffer and ArraySegment
- SIMD (with RyuJIT)
- Profiled code cache for JIT code background generation between executions (System.Runtime.ProfileOptimization)
Accessing data on the stack instead of the heap is the #1 saver of execution time, in my experience. But your bottlenecks might be different. Locally scoped value-type variables are generally on the stack. Object-scoped and static fields and properties are on the heap.
Writes to local variables seem to be faster than reads, IIRC. The fastest operators seem to be the bitwise instructions, IIRC. If running in 32-bit mode, try to work with 32-bit integers. If running in 64-bit mode, try to work with 64-bit integers.
Here's an example of a major, major improvement in performance
for(int x = 0; x < this.Width; x++)
{
for(int y = 0; y < this.Height; y++) { foo = bar; }
}Much faster version (due to storing a copy of Width and Height on the stack instead of the heap):
int width = this.Width;
int height = this.Height;
for(int x = 0; x < width; x++)
{
for(int y = 0; y < height; y++) { foo = bar; }
}My comment here describes roughly the approach I used to take advantage of stack-allocated memory (before Span<T> was available). https://news.ycombinator.com/item?id=15136627
If you can get rid of all heap lookups in your iterative loop, then you'll see a large speed boost if that was the bottleneck. Local variables exist on the stack, which tends to exist in the CPU cache when the current thread is active. https://msdn.microsoft.com/en-us/library/windows/desktop/ms6...
Unfortunately, method calls in C# have a much higher overhead than in C and C++. If you must do a method call in your loop, be sure to read this to see if your method can be inlined. Only very small methods of 32 IL bytes or less can be inlined: https://stackoverflow.com/questions/473782/inline-functions-...
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).
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!
On the other hand BEAM has AFAIK non-moving generational GC implemented in the aforementioned way which is in this case trivial as you don't need any write barriers and remembered set when heap objects are inherently immutable. (In this it is somewhat relevant that Boehm GC for some time had API that allowed you to signal the time extent of mutatibility of given heap object to the GC, AFAIK it is no-op since some 6.x version and the concurrent/incremental/generational bits of it work on basis of mprotect() and handling SIGSEGV)
.NET at one point stored an extra per-object word, which was either (via its LSB) a random hash code or a pointer to a metadata object that held the lock, etc.
Python did this cute thing where moved objects would get an extra word allocated to store the moved-from address, which was where the hash was stored.
Apple's plan for its (not-released) ObjC moving GC was to directly teach the GC about hash tables, so it could rehash on collection (!).
Do you know how other implementations handle this?
BEAM's GC is indeed a marvel both because of the immutability and also the enforced process isolation, so that each process can do its own GC independently. As you say, constrain the mutator...
As for having tight coupling between moving GC and hashmap implementation there is another reason why you want to do that: weak pointers and hashmaps that are either weak keyed or weak valued which both are things that are useful for the VM implementation itself (symbol tables, method dispatch caches, identity maps for FFI...).
I believe the GP was referring to PyPy.
From https://morepypy.blogspot.co.uk/2009/10/gc-improvements.html
> The hash field is not necessarily there; it is only present in classes whose hash is ever taken in the RPython program (which includes being keys in a dictionary). It is an "identity hash": it works like object.__hash__() in Python, but it cannot just be the address of the object in case of a GC that moves objects around.
https://www.erlang-solutions.com/blog/erlang-garbage-collect...
I think it’s moving, unless I misunderstood something?
Few years ago I read some paper from Ericsson that described non-moving generational GC explicitly designed for Erlang's data model which could even be implemented in terms of malloc()/free(), I'm not sure that it was relevant to how it is implemented in current BEAM.
Oh man JNI and Android.
I've never heard a developer curse up a storm like I did when one of my co-workers inadvertently stumbled across the 512 LocalRef limit(also a fun one) during an intermittent crash repro.
By the time he got done with his rant we had to talk him out of purchasing a one-way plane ticket to Mountain View.
Leaving aside the fact how Google treated Sun, the whole framework has a feeling that was written by former C and C++ devs, learning Java on the job while implementing Android.
So if a native java dev had written the whole plattform, how would he handle the limitations of the plattform any diffrently, except for alot of abstract wrapping and exceptional execptions?
There is a reason why there is no rte every time something comes to the limits of machine feasible. Projecting ones unwillingness to cope with reality on a developer with a difficult job - is sort(off, sad);
Any Java developer that used Java Card, J2ME or Embedded Java is quite aware of them.
A native Java dev would not have used Hungarian notation, snake case identifiers, allocation of classes just to fake out parameters which could be returned as result,have event handlers with unused parameters repeating AWT design errors and a couple of other things that I could still rant on.
Of course that leaves some memory management in Java, itself, but the price is usually fine for having void* access in C.
On top of that, having developers mixing GC compiled libraries with others compiled the traditional was a good recipe for random runtime crashes.
It works even when the object is usually thrown away but occasionally isn’t.
With the latest C# 7.x features will become easier to use it.
Typically this isn't a big problem - pinned data structures in .NET code are either pinned for short periods of time (to pass to native code), or are reusable large big buffers that stay pinned forever. Large buffers are always allocated in the large object heap right away. You can always allocate native memory directly in which case the GC doesn't care about it.
This may be changing since recent updates to C# and the runtime have introduced the concept of interior pointers to objects, where you can have a raw pointer to a field within a GCable object. Right now those are constrained to living on the stack only, so the period of time in which the object can't be moved/compacted as a result is relatively short.
GCHandle is a struct so you have to explicitly call GCHandle.Free().
Pins on the ephemeral segment are generally bad in that the quantum allocator has to be aware of them and squeeze objects between them.
The GC is not permitted to eagerly move pinned objects out of the heap. This is because there are two ways an object can be pinned: a pinning GC handle or a stack scan reports a local as pinned (e.g. the "fixed" keyword in C#). The GC does not know until a GC is already in progress that an object has been pinned and, at that point, it's not legal to move the object so it must stay where it is at the current point in time.
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.
There was always the DevTools vs WinDev differences regarding the role of .NET in Windows, and for a while it seemed Microsoft has happy having .NET just be good enough.
Thankfully they have changed their mindset and are improving .NET to be as close to C++ as possible, at least for 99% of the use cases where using a GC language is an acceptable trade off.
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?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.
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.
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.
X thing = new ...;
if (slowpath) {
unlikely_function(thing);
}
...
Even if unlikely_function isn't inlined, it can still perform scalar replacement, and push the allocation site into the branch (reconstructing the state of the allocated object as it would've been at that point), which is a big improvement.This in turn lets the inliner be smarter about what it does and doesn't inline, vs c2 which tries to greedily inline everything, partly to assist escape analysis
Well you assigned the instance to a global variable. That's how Java knows. If you can see that then so can the Java compiler.