<- Back To Home Page To Source File

.NET AOT Compilation


Intro

This Month i was interested in AOT Compilation in .NET. It is a new way to compile your .NET project. Microsoft is heavily investing in AOT compilation at the moment. Which is reflected in the changes that have come with .NET 8 and C# 12. I think AOT compilation is a very good direction for .NET and has a lot of applications.

What is AOT Compilation

AOT stands for Ahead-of-Time. With AOT compilation you can directly compile to native code. So what’s the difference to how we traditionally compile our C# applications? Usually when you compile C# the Roslyn compiler generates CIL (Common Intermediate Language) (Also sometimes called MSIL (Microsoft Intermediate Language) or just IL (Intermediate Language).) code which is executed by the CLR (Common Language Runtime). The JIT compiler which is part of the CLR then compiles it piece for piece into native code for the current platform and architecture while your application is running. AOT compilation on the other hand compiles your code directly into native code similar to how other languages like C/C++ and GO are compiled. This removes the need for the CLR. Note that some parts of the CLR like the garbage collector are is still included in the application. This is similar to GO which is also a garbage-collected language and also needs to ship a garbage collector with each application.

Pros and Cons of Having a Runtime

Pro:

Con:

Advantages of AOT Compilation

We have discussed the pros and cons of having a runtime. Now how can AOT compilation help?

Startup Time

AOT compilation can drastically reduce startup time. This is because the CLR itself also has a startup time and only after the CLR has started can your application start. At which point your code also has to get JIT compiled and only after all that will your first line of code get executed. For the ASP.NET API template the average startup times are 350ms for regular compilation and only 45ms for AOT compilation in the benchmark in this article. This is a very extreme example. The extra 300ms obviously make less of a difference when your application needs 5s to start. In conclusion, applications that have a short startup time by themselves profit the most by removing the JIT compilation step. This makes AOT compilation a great choice for deployments such as AWS Lambda functions or Kubernetes, as nodes have reduced startup time.

Interoperability

Being compiled to native code makes it a lot easier for other languages to use C# libraries. This article explains how to create a native library in C# (.NET 7) and how to use it in C++.

Availability

Support for AOT compilation was moved from experimental in .NET 7 preview 2. But in .NET 7 support for AOT was limited to console apps and class libraries.
In .NET 8 this support was expanded and also includes support for many ASP.NET application types. More about ASP.NET AOT compatibility and performance can be found here. In .NET 8 AOT compilation can also be used in some UI Frameworks. Avalonia UI and the Uno Platform both support AOT compilation. AOT was also mentioned in the WPF Roadmap 2023 as a long term goal.

Limitations

There are some limitations for AOT compiled applications. These are the main ones:

  1. Your application and it’s dependencies must support trimming.
  2. Your application must support compilation into a single file. (Single-file deployment)
  3. Most operations that require reflection are not compatible.

These limitations can make it difficult to make existing projects AOT compatible. For example if you are using AutoMapper your application is not AOT compatible. You would need so switch to a mapper that uses source generation instead of reflection like Mapperly.

Also note that AOT cross compilation only works across different architectures and not across different operating systems. More information about this can be found here.

How To do AOT Compilation

To AOT compile your project you will need to set the following property in your .csproj file.

<PublishAot>true</PublishAot>

If you now publish your project it will be AOT compiled.

.NET 7

dotnet publish -c release

.NET 8

dotnet publish

Starting from .NET 8 release is the default configuration for publish.

Set up The ASP.NET Web API (AOT) Template

  1. Check that .NET 8 is installed.

    You can do this by using the following command

    dotnet --list-sdks
    

    example output:

    teddy@teddy MINGW64 ~
    $ dotnet --list-sdks
    5.0.408 [C:\Program Files\dotnet\sdk]
    6.0.412 [C:\Program Files\dotnet\sdk]
    7.0.101 [C:\Program Files\dotnet\sdk]
    7.0.400 [C:\Program Files\dotnet\sdk]
    8.0.100 [C:\Program Files\dotnet\sdk]
    
  2. Install necessary tools.

    To be able to do AOT compilation we need some extra tools. Here is how to install them on your platform.

    On Windows:
    The easiest way to get the required tools on windows is by installing the “Desktop development with C++” package in the Visual Studio Installer.

    Visual Studio Installer Desktop Development With C++

    On Linux:
    The Tools required depend on the Linux distribution you are using. To find out which, it is easiest to just run the AOT compilation and looking which tools it is not finding.

  3. Create the project.

    Create the project from the webapiaot template with the dotnet new command.

    dotnet new webapiaot -n MyAOTWebAPI
    

    This template creates a new ASP.NET Minimal API that is AOT compatible. The AOT compatible Minimal API uses the new interceptor feature from .NET 8 instead of reflection to map endpoints.

    example output:

    teddy@teddy MINGW64 /c/DEV
    $ dotnet new webapiaot -n MyAOTWebAPI
    The template "ASP.NET Core Web API (native AOT)" was created successfully.
       
    Processing post-creation actions...
    Restoring C:\DEV\MyAOTWebAPI\MyAOTWebAPI.csproj:
      Determining projects to restore...
      Restored C:\DEV\MyAOTWebAPI\MyAOTWebAPI.csproj (in 364 ms).
    Restore succeeded.
    

    This will create the MyAOTWebAPI project.

  4. Publish project AOT compiled

    Run the following command to publish the project

    dotnet publish ./MyAOTWebAPI/MyAOTWebAPI.csproj
    

    example output:

    teddy@teddy MINGW64 /c/DEV
    $ dotnet publish ./MyAOTWebAPI/MyAOTWebAPI.csproj
    MSBuild version 17.8.3+195e7f5a3 for .NET
      Determining projects to restore...
      Restored C:\DEV\MyAOTWebAPI\MyAOTWebAPI.csproj (in 1.97 sec).
      MyAOTWebAPI -> C:\DEV\MyAOTWebAPI\bin\Release\net8.0\win-x64\MyAOTWebAPI.dll
      Generating native code
      MyAOTWebAPI -> C:\DEV\MyAOTWebAPI\bin\Release\net8.0\win-x64\publish\
    

    On the last line of the output you can see where the published AOT compiled application is located. In this example i have created a win-x64 application. This application will run on 64-Bit Windows operating systems running a CPU with the x86 architecture.

  5. Running the project.

    First navigate to the output folder.

    example:

    cd ./MyAOTWebAPI/bin/Release/net8.0/win-x64/publish
    

    Now run the compiled application. The filename of the application depends on the platform it was compiled for (windows: MyAOTWebAPI.exe, linux: MyAOTWebAPI).

    on windows:

    ./MyAOTWebAPI.exe
    

    on Linux:

    ./MyAOTWebAPI
    

AOT Configurations

Most of the information about this comes from the “Optimize AOT deployments” and “Trimming options” article.

Size or Speed

The OptimizationPreference property that can be set in the .csproj. This decides the priority of the compiler between creating the smallest application size and the fastest application speed. By default, the compiler chooses a blend between both. For the webapiaot template (MyAOTWebAPI) compiled for win-x64 this creates the following application sizes:
size => 8.8 MB
not set => 9.1 MB
speed => 9.7 MB
This option would most likely make a larger impact in a bigger application. Options:

Trimming

As mentioned before AOT compilation also requires trimming. Configuring the trimming can further reduce the application size. Here are some options i found interesting.

TrimmerRemoveSymbols

This property determines whether to remove symbols. Options:

DebuggerSupport

This property determines whether debugging information and symbols are included. Options:

UseNativeHttpHandler

For Android and IOS this property determines whether to use the platform implementation of HttpMessageHandler instead of the .NET implementation Options:

StackTraceSupport (.NET 8+)

Setting this property to false removes code that is required for the runtime to create stacktraces. Note this doesn’t remove stacktraces completely. The stacktraces will just start looking like this:

fail: Microsoft.AspNetCore.Server.Kestrel[13]
      Connection id "0HN0MEBT0QLKU", Request id "0HN0MEBT0QLKU:00000001": An unhandled exception was thrown by the application.
      System.Exception: My Exception
         at MyAOTWebAPI!<BaseAddress>+0x17a98c
         at MyAOTWebAPI!<BaseAddress>+0x17b73c
         at MyAOTWebAPI!<BaseAddress>+0x1159c9
         at MyAOTWebAPI!<BaseAddress>+0x116777
         at MyAOTWebAPI!<BaseAddress>+0x1309c9
         at MyAOTWebAPI!<BaseAddress>+0x402f6f

instead of this:

fail: Microsoft.AspNetCore.Server.Kestrel[13]
      Connection id "0HN0MECLC6T9P", Request id "0HN0MECLC6T9P:00000001": An unhandled exception was thrown by the application.
      System.Exception: My Exception
         at Program.<>c__DisplayClass0_0.<<Main>$>b__2(Int32 id) + 0x3c
         at Microsoft.AspNetCore.Http.Generated.<GeneratedRouteBuilderExtensions_g>F69328E0708B4B584C5AACA22FE2C51A1CF192D6622828F613FC57C583CA77B63__GeneratedRouteBuilderExtensionsCore.<>c__DisplayClass3_0.<MapGet1>g__RequestHand
ler|4(HttpContext httpContext) + 0x17c
         at Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext httpContext) + 0x299
         at Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.Invoke(HttpContext httpContext) + 0x377
         at Microsoft.AspNetCore.HostFiltering.HostFilteringMiddleware.Invoke(HttpContext context) + 0x109
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.<ProcessRequests>d__238`1.MoveNext() + 0x36f

Options:

My Experience

I have created a console app version of the classic mobile game Timberman called TreeCutterTUI to test the new AOT compilation. I have developed this game both from a Linux and Windows and the AOT compilation worked perfectly on both.

Closing Notes

It was interesting to see that some .NET features only work in a JIT compiled environment and how to work around this with things like source generators. For next month i will probably choose the topic “JIT compilation”. While researching this month’s topic i fell down a rabbit hole on the C# JIT compiler. It is a very interesting topic.