76 comments

[ 1.2 ms ] story [ 33.7 ms ] thread
It's been a few years since I used .NET, but the self-contained deployment story sounds like a big win that I used to really want:

"Self-contained deployment. Unlike FDD, a self-contained deployment (SCD) doesn't rely on the presence of shared components on the target system. All components, including both the .NET Core libraries and the .NET Core runtime, are included with the application and are isolated from other .NET Core applications. SCDs include an executable (such as app.exe on Windows platforms for an application named app), which is a renamed version of the platform-specific .NET Core host, and a .dll file (such as app.dll), which is the actual application."

So, this IL Linker is in support of that story, and aims to reduce the over all size of self-contained applications... Maybe it's time to start tinkering w/ .NET again.

My company has some apps that we need to install on our customers servers. A linker would have been amazing for us 7 years ago as we could have targeted the (as of then new) 4.0 runtime and redistributed or binary. But as it was, we could only expect the 2.0 runtime to be installed so we can't really leverage a lot of stuff that's been out since then and had to write our own hacky versions of lots of common things.

Nowadays with dotnet core you can bundle the entire runtime with you binary. The linker is cool, in that it will create a strictly smaller binary, as in "hello world" might be 12mb instead of 50mb. That's cool, but it makes me wonder if .NET would be in a strategically better place today if the linker were present 10 years ago. How many companies didn't choose to write their app in c# simply because a customer might not have the runtime installed?

In any case I'm really impressed with everything Microsoft is doing with dotnet core and hope that I get to use more of it.

> How many companies didn't choose to write their app in c# simply because a customer might not have the runtime installed?

More than enough to make a difference against Java.

(comment deleted)
It certainly would. The reach of .NET was artificially restricted in many respects by making it a component of Windows. Mono provided the ability to just ship the runtime alongside the app, but Mono was also not "enterprise grade"; and of course there were always the missing bits and pieces, especially on the desktop front where such deployment made most sense.

It took a Microsoft that understood open source and non-Windows platforms, and was serious about it, to ultimately get us to the point where .NET Core could be a thing.

Until .NET Core provides support for many of the .NET Framework APIs still lacking from .NET Standard 2.0, many companies won't consider it "enterprise grade".

Examples on our case, WPF, WCF, ODP.NET drivers for ADO.NET.

I think .Net Core will never include WPF.

As far as I know, there are not plans for cross-platform GUI on .Net Core. And WPF is pretty much a dead technology.

I really want C#/.NET to gain ground against Java.

I started my career working for a .NET shop a few years ago, and immediately fell into a love/hate relationship with the technology stack.

I loved C#, as it seemed to be a fantastically well-designed language that had learned a lot from Java's mistakes. Visual Studio was great, too.

However, as a longtime Linux and open source supporter, I loathed Windows and Microsoft's software culture. And honestly, the .NET open source culture - even now - is kind of lacking. C# shops typically wait for some canonical solution from MS rather than creating and sharing libraries themselves.

I really, really like the new direction that Microsoft is taking, and I hope that the open source community will embrace C# in a bigger way.

.NET for over a decade and "C# shops typically wait for some canonical solution from MS" rings quite true.

With NuGet I don't think it's as bad as it was, but I just caught myself hesitating when I found out this week that .NET has deprecated email sending functionality in 4.7 (or Core?) and is now recommending open source alternatives.

If .NET (using C#) can produce a native binary I'll switch to that for as many projects as I can. Currently I use C++, mostly to interface with the OS. I love the feel I get when working in C#. Everything is so nice and cozy.

Memory: The memory usage "looks" bad. I suppose maybe GC would kick in when it needs to. It has not been a problem for me, but when you see the code in C++ taking 1MB ram and a C# implementation taking 10MB, well, you have to wonder.

Performance: C# performs bounds checking, which I take it is a significant performance hit. I've not had this matter yet, but I've not converted everything over to .NET.

There's definitely a cost to all that coziness; it's all about tradeoffs. Garbage collection, bounds checks, APIs that trade memory efficiency for ease-of-use, to name a few.

It's not to say that C# will always be 10x memory usage, however, it will always be somewhat higher. Even with a linker, when you run a .NET app, it's not just loading your C# code, but also .NET code that you call into (linker helps keep this slim) and of course the Common Language Runtime C++ code that executes your app and manages its memory.

> APIs that trade memory efficiency for ease-of-use

This is currently being worked on. With the addition of Span<T>, many APIs will soon have a version that does not allocate and instead use a buffer you pass in (which can be managed or unmanaged).

> "If .NET (using C#) can produce a native binary I'll switch to that for as many projects as I can."

https://docs.microsoft.com/en-us/dotnet/framework/net-native...

I like how Microsoft added this but the major drawback is it is Windows 10 only.
There is also mono aot as possible alternative.
Yep, but it isn't quite the same. Mono AOT still requires Mono to run the executable, it just doesn't use the JIT engine (when passing in --aot=full command line when executing your program). It doesn't create a native executable.
Sure it does, how do you think Xamarin creates native executables for Apple devices?
Oops, you're right. I misread the docs. Sorry about that!
they also support command line apps...and linux. its on github.
> Performance: C# performs bounds checking

It does (The JIT that is), but it also often does not. For example a regular for loop does not bounds check anything. The newer x64 JIT ("RyuJIT") is a whole lot smarter than the legacy x86 jit was.

I imagine that once you start producing proper native binaries using whatever backend you want instead of a JIT that has a requirement of being FAST in addition to making code that is FAST, you can get even more optimization done.

Also, bounds check are becoming cheaper and cheaper as memory fetches become (relatively) more expensive with every hardware generation.

C++ code is usually faster than C# code but bounds checking is probably not the biggest culprit. If you use "idiomatic C#" with reference types, most operations have a lot of memory fetch overhead with pointer chasing etc.

It's possible to write pretty efficient C# code, and it can still look pretty nice. You end up using arrays more, structs more, obviously worrying about AoS vs SoA more. You should also look at the new ImmutableArray types that look like List<T> but have effectively no overhead compared to a plain array. After that comes Span<T>, which makes even more exotic things possible.

Using things like stackalloc, malloc, and local-only references, It is possible to implement algorithms in C# which can run at blazing-fast speeds, sometimes hundreds of times faster than idiomatic C#. C#-style arrays would not make the cut.

The optimal thing to do would be to memcpy several KB of data at a time from your heap-based memory, into your stack-allocated memory, do your processing in the optimized inner-loop, then commit the data back to the heap. I had to do this once to increase the throughout of a cluster of image processing servers.

I often scoff at the idea that C# isn't a good choice for performance-intensive tasks.

At some point however, the C# becomes so in-idiomatic or convoluted that it's just C in C# clothing. So one always needs to ask if it's time to just offload a task to a native library. It's convenient to have a single toolchain and not mess with a native toolchain though.
Especially since with native you're giving up AnyCPU, unless compiling the native parts for every platform you care about. I'd also argue that even unidiomatic C#'s semantics are less prone to UB than switching to C.
> If .NET (using C#) can produce a native binary I'll switch to that for as many projects as I can.

It's coming... https://github.com/dotnet/corert

I'm sure there's some reason this isn't the answer, but haven't we had ngen since time immemorial? Aren't all of the assemblies that get shipped with Windows actually compiled to the native architecture?
NGen is more like a cached result of the JIT output. It was never particularly fast, and in some cases actually worse than normal JIT. The main benefit you gained from it was assembly load times.

And it most certainly didn't do anything about the dependencies on the runtime (VM, GC etc).

>If .NET (using C#) can produce a native binary I'll switch to that for as many projects as I can.

We've been compiling our large C# games to native code for some years now because iOS only supports signed code. The main problem is you can never JIT; this creates all sorts of limitations with generics, dynamics, expressions and reflection.

Xamarin has become better and better at handling generics but there are still a couple of things they just can't do anything about.

>Memory

Yes, the memory looks bad because it will not get completely collected until needed. The framework code is big, but that's a flat cost. It might take a little more memory for the runtime up keeping, but it is not 10x. However, memory profiling is a breeze compared to native, so it becomes easier to optimize on a large complicated project.

>Performance

If you don't want to bound check there are a lot of solutions, from simple to advanced. Most of the time we end up just iterating the whole array anyway, and in those case, there are no bound checks.

In the rare case where we really need raw power (graphics, path finding, physics, large file access), we use native code and thin wrap it with P/Invokes.

Curious if you deal with a lot of float32 math (the usual sin cos log tanh) and if so what do you use.
For the heavy lifting such as scene transformations and animations we delegate in batches to a SIMD optimized native lib.

For the rest, we use System.Math. They are not used enough to be a significant bottleneck.

The nice thing about C# is that, unlike Java, you actually get all the low-level things. They're just tucked away, out of sight of your average coder who's more likely to shoot themselves in the foot with them. But if you know how to use them, it's all there. Let me enumerate.

C# has value types, which follow the same memory model as in C (i.e. stack-allocated for locals, embedded directly into the outer object as fields). You can request explicit layout mode, whereby you can set an offset for every field manually - if you set them all to zero, you get a C union. Alternatively, you can request automatic layout mode, where the JIT is allowed to reorder the fields in arbitrary ways to optimize memory and/or access, which is something you don't even get in C.

It has raw pointers. Unlike object references, these have the usual C semantics - there's no GC involved there, and you're responsible for keeping the objects alive, so you can have dangling pointers etc. No boundary or null checks, either - it's zero overhead. You can do pointer arithmetic on them, including indexing with []. If you P/Invoke malloc or equivalent, this gives you C-style heap allocated arrays.

It has stackalloc, which is a language operator that's equivalent to the non-standard alloca() function in C - allocate a chunk of memory on the stack, and return a pointer. This immediately gives you stack-allocated arrays as flexible as C99 VLAs.

With generics, if a generic type parameter is a value type, the specialization for that type is separately JIT-compiled and optimized. This can be used in a way very similar to C++ templates, for zero-overhead inlined callbacks and similar shenanigans.

All in all, if you want to write high-perf code in C#, you certainly can. You'll hit the limits of the JIT optimizer soon enough - it can't be as good as a full-fledged AOT C++ compiler, say - but you can certainly shed most of the overhead associated with a managed language.

In Java some of them are there, although the Unsafe package is not sanctioned, and marked to be replaced by something safer that can provide the same capabilities (e.g. VarHandles on Java 9).

As for the rest at least we can hope Java 10 brings them.

You missed a few C# goodies from 7.0 up to 8.0, coming from the experience with Midori.

The goodies in question are local refs and ref returns, right? Which really only make a difference when you're writing memory safe code. If you're dropping down to "unsafe" and manual memory management for other things, then refs don't do anything that raw pointers don't.
I thought this technique of eliminating dead code was called tree-shaking. And linking to be something entirely different: linking assemblies at (compile|run|analysis)-time.
I've only ever heard it called dead code elimination. Tree shaking appears to be more a javascript thing? Not sure I don't participate in that community at all.

https://en.wikipedia.org/wiki/Dead_code_elimination

It's an old Lisp term going back decades. Strictly speaking it's not the same as interprocedural DCE, because due to the dynamic features of Lisps you might need the user to specify the extent to which they are willing to disable the dynamic features to save space.
Gotcha, would it be fair to characterize it as tree shaking is more white listing things that have been used versus eliminating code paths that cannot be accessed?
Yes dead code elimination is also called tree shaking and it is typically done at compile time. However for .NET, since you can use reflection to load any type in an assembly and call any method dynamically, removing a method is isn't statically called could cause you to fail at runtime.

Since reflection and dynamic invocation is a major feature of the runtime, they have avoided this optimization before.

> However for .NET, since you can use reflection to load any type in an assembly and call any method dynamically, removing a method is isn't statically called could cause you to fail at runtime.

Indeed. it's not that common, but I would expect it to happen. One example is that many IoC Containers allow you to scan assemblies and register all class + interface pairs that match given rules.

There has to be some safety-hatch to express "yes, I really want to keep that class and its methods. Don't strip them out".

This seems to be documented here: https://github.com/dotnet/core/blob/master/samples/linker-in...

Going from over 46MB to under 12MB seems impressive on a relative scale, but if this is the code of the "dotnetapp-selfcontained" example,

https://github.com/dotnet/dotnet-docker-samples/blob/master/...

That is still, absolutely speaking, twelve million bytes for not much more than "Hello World" levels of functionality, which means there remains plenty of room for improvement. I estimate the lower limit for this particular app is somewhere in the hundreds of bytes, most of it being string constants.

I see this fallacy repeated often. "Hello World is 12 MB - just imagine how big a real app will be!!!"

Hello World doesn't benefit from that 12MB. Your real app would.

12MB is not for Hello World. 12MB is for Hello World + Common Language Runtime (garbage collection, bounds checking, memory protection, execution environment etc.) + the .NET standard libraries (LINQ, task parallel library, etc.)

You're probably going to need those things in a real app.

If you're actually doing Hello World -- and that is all you're doing -- and if using 12MB of your 16GB of RAM is unacceptable for you, yes, by all means write some native code.

Put another way: what should be left after linking is just the parts of the CLR + BCL that the runtime needs to function. Apparently that takes 12MB today. There is a JIT, GC and Execution Engine all in there.

The number might shrink as the linker gets more aggressive but i wouldn't count on it much.

The application IL code is perhaps 50 bytes to for hello world.

12 megabytes doesn't sound too bad for the things you mentioned, especially if it's a constant cost.
> The application IL code is perhaps 50 bytes to for hello world.

A Hello World assembly in .Net Core 2.0 is 4.5 kB.

Out of that, the IL is 11 bytes, though that doesn't even include the "Hello world!" string.

I see this fallacy repeated often. "Hello World is 12 MB - just imagine how big a real app will be!!!"

You're probably going to need those things in a real app.

I'm not saying that. From the page itself: "The linker removes code in your application and dependent libraries that are[sic] not reached by any code paths. It is effectively an application-specific dead code analysis"

Going by that description, Hello World should be the most trivial of test cases for removing unneeded cruft, and yet it still leaves plenty behind.

Also, don't assume that everyone has 16GB of RAM (I have a tiny fraction of that), or that even if they do, your app should be using all of it.

The answer is in the quote "The linker removes code in your application and dependent libraries...". That doesn't include the Common Language Runtime.
Excellent point. There are two major caveats today:

- no removal of native code. - no removal of code from the base managed assembly.

We would like to fix both and both are a large win.

I wonder how this interacts with the "no-PIA" feature, where the compiler would embed and dead-strip COM interop types. Seems like some of that system's guid-based type identity attributes might be relevant.
This is (at least for now) only for .Net Core, which does not support COM interop, I believe.
Can you create a self-contained deployment (SCD) of a GUI app, or does it only work for console apps?
We currently have no GUI story for .NET Core. At the point that one gets added, yes, this will totally work.
The technology will work with any kind of app. I know that some folks working on one of the bigger UI frameworks, Avalonia (https://github.com/AvaloniaUI/Avalonia) have toyed with it recently. One thing to keep in mind is that lots of these frameworks do dynamic, reflection-based data binding, which might be easily broken if the linker removes too much stuff. You can tinker with the settings of the linker to force it to preserve things that are actually necessary.
Fun fact, the Mono Linker project started 10 years ago during the second edition of the Google Summer of Code!

It was originally used to reshape the entire Mono class library into a subset to expose the Silverlight class library API surface for Moonlight.

It was then used to link iOS and Android applications in MonoTouch and Mono for Android, and Xamarin continued to use and improve it.

The Mono Linker has an open architecture making it reasonably easy to customize how it processes code , and detect patterns specific to each platform to link them away. Xamarin added linker steps to do more than tree-shaking, and remove dead code inside methods. For instance:

  if (TargetPlatform.Architecture == Architecture.X64) {
    // ..
  }
The entire if body can be removed if the linker knows that TargetPlatform.Architecture will not be X64.

And now, it's the base for the .NET Core linker. Quite a journey!

This is neat as it brings smaller DLL sizes. It does not include self-contained executables that can be shipped, however. You still end up having to ship a directory full of DLLs. I've been wanting self contained executables for a while now. CoreRT (.NET Native) seems to have been put on the backburner, plus sometimes the JIT case is faster.

Source: discussion at https://github.com/dotnet/core/issues/915#issuecomment-32608...

Not sure about .NET Core as I haven't used it yet, but you can use ILRepack to merge DLLs into the executable so you don't need to ship a directory full.

https://github.com/gluck/il-repack

We do this at work with one of our utilities

Now they just need a modern (XAML, QML) cross-platform UI story and they can eat Qt and Electrons lunch.
Nice to see this progress, hopefully it's a serious part of the roadmap and not just a side experiment.
> "The linker removes code in your application and dependent libraries that are not reached by any code paths. It is effectively an application-specific dead code analysis."

Putting aside the discussion on whether this is a good practice, I assume this would remove methods that are only called dynamically, or via reflection.

Don't forget to check the license of every library, even many of the more permissive ones will require an attribution notice.