A More Efficient Code in .NET Thanks to Native AOT

In the latest versions of .NET, the Microsoft team has been very focused on performance. One of the features they've introduced is Native AOT Compilation. Here, we’re going to see how it works, its benefits, and some use cases.

 

 

1 - What is Native AOT in .NET?

As everyone knows, when you compile in .NET, what you’re actually doing is compiling into what’s called IL or Intermediate Language. Later, the .NET Runtime (CLR) takes care of compiling it into machine code via the Just In Time (JIT) compiler.

 

 

That’s how .NET has worked for years, but at the netconf 2022 they showcased a preview of what we now know as Native AOT.

What Native AOT allows us to do is compile our C# code into native code directly on the machine we’re building on, no need for the intermediate step of compiling to IL.

In the case of Windows, for example, it gives us an .exe;

    • Note: Technically, it compiles to IL and then from IL to machine code, but all of this is done as part of the same compilation, so it's transparent to the user.

 

 

2 - Advantages and Disadvantages of Native AOT in .NET

As with everything in programming, there are pros and cons. Before continuing, I want to point out that, at my current client, Native AOT is not yet used, but there are plans to start implementing it during 2024; this information is mainly taken from talks and, of course, some tests I've run myself.

 

The main point of Native AOT is performance. By compiling directly to machine code, we don’t have to waste time compiling from IL to machine code at runtime, so it’s faster, especially on first runs.

For those who don’t know, the JIT compiler runs the first time a method is invoked, and then it stays compiled.

Think about an Azure Function/Lambda, which are quite popular right now, a lot of time is spent on startup. Native AOT drastically reduces this initialization time, and therefore the economic cost.

 

 

Of course, compiling to machine code for a specific machine means that the same compiled code won’t work on multiple systems. For example, if your machine is Windows, you can only compile for Windows; if you want to compile for Linux, you need to do so on a Linux machine. Without Native AOT, you compile on your machine and the resulting .dll can be run anywhere.

 

But, as expected, there’s a reason for this. I said earlier that when compiling in Windows you get an exe, and, of course, it can be executed, meaning everything your app needs to run is inside that executable. The machine running the code doesn’t need to have the .NET runtime or any dependencies installed. The same is true for Linux, or for 32-bit and 64-bit architectures, even if both are Windows.

 

At the same time, this leads to another side effect: both the compilation time and the application size are much larger when using Native AOT than with default compilation.

 

Finally, even though this is more of a temporary point, we can't make all applications AOT. For your app to be compatible with Native AOT, all the dependencies you’re using need to support it. Generally, many do, but, for example, Entity Framework Core is not supported, WebAPI is not supported; when you use reflection, some things are supported and some aren’t, so for now we can't say it’s a 100% functional solution.

 

 

3 - Native AOT Example in .NET

Now let's look at an example and compare a “normal” application and one with Native AOT, the app itself will be the same: the minimal API provided by Microsoft out of the box, since minimal API is supported by Native AOT.

So, let’s see.

 

First, you need to go to the Visual Studio installer and install the Desktop development with C++ packages; technically you could install them manually, but this way you get everything in one click.

c++ desktop development

By the way, these libraries take up more than 6GB, so it may take a while.

 

Once you have this, create a project, you can search for AOT or just do it directly:

aot project

There are several differences compared to a regular minimal API. Here’s a post from Microsoft where they explain them.

But I’d highlight two main points: when creating our builder, we’re using a SlimBuilder instead of a regular builder. This is because it contains the absolute minimum you need to run the application.

var builder = WebApplication.CreateSlimBuilder(args);

And the other main detail is that in the .csproj of the project, there’s a property called PublishAot set to true.

<PublishAot>true</PublishAot>

To keep it as realistic as possible, the code is as similar as possible between both; both have the same endpoints with the same functionality, for example, Swagger has been removed from the web API.

 

3.1 - Size Difference in Native AOT

In both cases, we can use dotnet publish; in fact, for Native AOT, it’s mandatory. For the most accurate results, we do it in both cases.

 

Here are the results for both versions:

aot vs normal size difference

As you can see, one is a .dll (and an executable that runs it), and the other is purely an executable, with no surrounding dll, and notably, it’s much larger.

And obviously you can't see this in a picture, but when I publish the video you'll see that compiling with Native AOT takes considerably longer.

 

The Native AOT version is located at \bin\Release\net8.0\win-x64\publish

The default version: DefaultExample\bin\Release\net8.0\publish

As you can see, the Native AOT version includes the win-x64 of the machine and architecture used for compilation;

 

 

3.2 - Performance Difference in Native AOT

 

For this example, I wasn’t sure whether to simply use the basic example or do something a bit extra. In the end, to make it more realistic, I decided to make an app that queries the database for simple values, nothing complex, since EF isn’t supported, but Dapper is (although not fully).

 

Additionally, I added a middleware to both that calculates execution time.

Both applications are the same, the only difference is the compilation, and the AOT version needs Dapper.AOT. Here are a few example runs.

native aot vs normal

This example is simple, so we’re talking just several microseconds difference, but it’s already substantial if you have thousands of calls per minute.

Throughout the year or with future versions of .NET, I’m sure we’ll see improvements in third-party libraries we use, so they can be compiled with NativeAOT.

For now, it’s a very good start.

This post was translated from Spanish. You can see the original one here.
If there is any problem you can add a comment bellow or contact me in the website's contact form

Uso del bloqueador de anuncios adblock

Hola!

Primero de todo bienvenido a la web de NetMentor donde podrás aprender programación en C# y .NET desde un nivel de principiante hasta más avanzado.


Yo entiendo que utilices un bloqueador de anuncios como AdBlock, Ublock o el propio navegador Brave. Pero te tengo que pedir por favor que desactives el bloqueador para esta web.


Intento personalmente no poner mucha publicidad, la justa para pagar el servidor y por supuesto que no sea intrusiva; Si pese a ello piensas que es intrusiva siempre me puedes escribir por privado o por Twitter a @NetMentorTW.


Si ya lo has desactivado, por favor recarga la página.


Un saludo y muchas gracias por tu colaboración

© copyright 2025 NetMentor | Todos los derechos reservados | RSS Feed

Buy me a coffee Invitame a un café