Crossgen as build step with .NET Core 3.0
From NGEN to Crossgen. Use Crossgen to produce Ready-to-Run Images with dotnet publish.
.NET Conf 2019 Countdown series
I’m excited to be part of the .NET Conf with this every day mini-post series until the 23th September.
- IL-Linker in .NET Core 3.0
- The Bundler in .NET Core 3.0
- Crossgen as build step with .NET Core 3.0
- The IAsyncDisposable interface in .NET Core 3.0
- Platform intrinsics in .NET Core 3.0
- .NET Conf 2019 is right ahead
Prerequisites & Setup
From NGEN to Crossgen
As you maybe know, your source code is compiled (by Roslyn) into IL-Code (CIL, Common Intermediate Language): a set of instructions (kind of low level language) that is platform- and processor independent. Unlike native code, before your .NET application can run, it requires a JIT (Just-in-Time) compiler that translates the IL-Code into native code (that is platform- and processor specific).
This JIT compilation (done by RyuJIT) into native code does take advantage of optimized or specialized instructions of the underlying platform/CPU and can also re-optimize methods on the fly (e.g. Tiered Compilation) - that’s great isn’t it?
But this process of JIT compilation isn’t free and is noticeable especially on the startup - as always software development is a trade-off. How could we mitigate that? A solution introduced nearly at the beginning of .NET era: the tool NGEN (.NET Framework 2.0 and onwards).
This NGEN compiled the .NET assembly into a special native image with native code. This concept is called AOT (ahead of time) compilation, kind of opposite of JIT. Unfortunately NGEN had by design the drawback, you couldn’t create the native images up-front as part of your application build and ship it to the client…
The successor: Crossgen
Let’s try out Crossgen that ships as part of .NET Core 3.0 with a console app:
It is simple to opt-in this feature - add the element
<PublishReadyToRun>true</PublishReadyToRun> to your .csproj:
After that, publish your console app:
Enabling log verbosity on MSBuild reveals the execution of specific targets to create the Ready-to-Run Images with Crossgen.exe:
When you look at the publish output directory, there is no notable difference, except the console app dll size:
- Not Ready-To-Run: 4,00 KB (4.096 bytes)
- Ready-To-Run: 5,50 KB (5.632 bytes)
The Crossgen processed assembly is slightly larger and has in the PE file header the flag
CorFlags.ILLibrary=0x00000004 in contrast to the other assembly
Crossgen uses internally RyuJIT to compile the native code and includes it into your .NET assembly - but preserves the IL-Code. The IL-Code is kept for some scenarios and could also be used as fall-back when the native code doesn’t match the underlying platform. The native code reduces workload for the RyuJIT during startup-time of your application, thus it’s faster loaded.
Further, you could exclude assemblies from beeing processed by Crossgen within .csproj:
The result would be a self contained, single file application with pre-compiled native code.
Limitations & .NET Native
- ReadyToRun is supported on .NET 3.0 and later. You can’t use it with earlier versions (e.g. .NET Core 2.0 or .NET Framework).
- ReadyToRun works only with SCD (Self-Contained Deployment) apps (FDD support is planned).
- ReadyToRun does not support cross-targeting: For instance linux-x64 images must be compiled on that environment.
.NET Native is a completely separated tool-chain that is only usable for UWP (Universal Windows Platform) Apps. .NET Native doesn’t require any JIT and has a optimized and minimalistic runtime with some restrictions (e.g. Relfection API).
I’m super excited about some new capability that we get with .NET Core 3.0 SDK. This AOT option is worth trying especially for larger applications. Will you try out this for your application? Let me know, what you think.