Matt Warren, a .NET developer, writes about CLR internals, runtime design, performance and the history of .NET.
Commenters call it an incredible resource for CLR internals and value its write-ups on generics, CoreRT and .NET talks.
Java did this in a very complicated but backwards-compatible way. (Including bytecode level compatibility.) Here is a detailed FAQ [1] devoted to the intricacies of Java generics, covering erasure, bounded and unbounded wildcards, special cases for raw types and arrays, workarounds for primitive types, interaction with inheritance, etc.
It was a big achievement, but also something to learn from. It’s reasonable that other language designers would want to do better.
The C# designers tried to improve on what Java did, reconsidering many of their design decisions [2]. Apparently it was a five year effort, including inventing novel runtime mechanisms. They had different goals, particularly supporting multiple languages and cross-language compatibility.
Go’s generics support is another years-long effort. They reconsidered everything again in a new context, because the Go language doesn’t have the same features or design constraints. That’s also a big achievement.
This only looks like the “exact same thing” if you ignore all the history. Language design is all about the details. Every language feature interacts with every other language feature.
The “peanut gallery” is a lot of random people, some of whom don’t know anything about designing or maintaining a production language. If you really want to know about this stuff, I suggest reading the papers by people who actually did it.
[1] http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html
[2]…
For those interested in more details about .NET garbage collector here is some reading.
Garbage Collection Overview
https://msdn.microsoft.com/en-us/library/0xy59wtx(v=vs.110).aspx
Fundamentals of garbage collection
https://msdn.microsoft.com/en-us/library/ee787088(v=vs.110).aspx
Visualising the .NET Garbage Collector
http://mattwarren.org/2016/06/20/Visualising-the-dotNET-Garbage-Collector/
Clr Book of the runtime : Garbage Collection
https://github.com/dotnet/coreclr/blob/master/Documentation/botr/garbage-collection.md
Maoni Stephen's Blog
https://blogs.msdn.microsoft.com/maoni
https://blogs.msdn.microsoft.com/maoni/2004/06/15/using-gc-efficiently-part-1/
https://blogs.msdn.microsoft.com/maoni/2004/09/25/using-gc-efficiently-part-2/
https://blogs.msdn.microsoft.com/maoni/2004/12/19/using-gc-efficiently-part-3/
https://blogs.msdn.microsoft.com/maoni/2005/05/06/using-gc-efficiently-part-4/
There is also a refactoring effort to standardize the GC<->EE interface
https://github.com/dotnet/coreclr/projects/3
I would link to the source code of the GC itself, but it's a single 35KSLOC file.
On .NET Core's focus, that's not actually true:
http://mattwarren.org/2017/12/15/How-does-.NET-JIT-a-method-and-Tiered-Compilation/
Android optimizes for battery life, but it's also worth noting that Dalvik was a really rudimentary JIT, having no benefits from JIT compilation, only drawbacks, the ART with AOT being a good upgrade.
But tiered compilation is in a different league, being about speculating what's going to happen depending on what the process has witnessed thus far. The point of tiered compilation is to profile/guard stuff at runtime and recompile pieces of code based on changing conditions, which is how you can optimize virtual call sites or other dynamic pieces, which you can't do ahead of time because the missing part is the decompiler which can revert optimizations based on invalidated conditions.
It's really interesting actually, because you can profile a C++ app and use that to optimize your AOT compilation, but the compiler is still limited by the things it can prove ahead of time, or otherwise it would be memory unsafe.
There's a few challenges with doing something like this.
- arrays (which most collections use under the covers) will need to allocate `elements*size_of_struct`. Unless the structs are the same size, you'd have to do some level of thunking/offsetting.
- Even if they are the same size, when you're iterating the list, how would the runtime know whether it's `A`, `B`, or `C`? Structs don't have header information that can be used to infer the type, it's only whatever fields you've defined (with padding potentially). Part of 'boxing' is essentially thunking the struct by wrapping a methodtable for the struct into the object [0].
If you need something like polymorphism with structs, your best bet is to make the struct itself quasi-polymorphic (i.e. with enums) and/or start abusing generics in some fashion I haven't yet figured out.
[0] - https://mattwarren.org/2017/08/02/A-look-at-the-internals-of-boxing-in-the-CLR/
It looks like the number of threads in the threadpool is fixed. If have 1 one thread in the thread pool, is it possible to dead lock it by queuing a Task A the blocks waiting for Task B to do something, where A starts executing before B?
Do you think it would make sense to dynamically adjust the number of threads in the pool? An example of a thread pool that dynamically adjusts the number of threads is .NET. The algorithm is described by Matt Warren[1] and a paper[2]. In addition to the C++ version Matt links to, there is a C#[3] implementation.
[1]: https://mattwarren.org/2017/04/13/The-CLR-Thread-Pool-Thread-Injection-Algorithm/
[2]: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.487.7627&rep=rep1&type=pdf
[3]: https://github.com/dotnet/corert/blob/master/src/System.Private.CoreLib/src/System/Threading/ClrThreadPool.HillClimbing.cs
The CLR is quite terrible at small programs, as you're seeing.
That being said, the huge "Image" section doesn't have to be as big as you're seeing. If you look at the breakdown [0], then you can see some debugging, in the form of "TraceDLL". He also mentions that the four biggest memory eaters: System.Private.CoreLib.dll, coreclr.dll, clrjit.dll and CoreRun.exe are probably bigger than normal, because he's using a debug version of the CLR.
Outside those five, every other piece of memory is shared, which changes how you see its allocation.
I'd expect it'd change a measurable amount, but as before... The CLR is best when handling bigger programs, where JITing can actually make sense.
[0] http://mattwarren.org/images/2017/07/Image%20Memory%20Usage.png