.NET8 ultimate performance optimization AOT

Preface

.NET8 optimizes performance in all aspects, so AOT precompiled machine code is no exception. This article looks at the optimization of AOT.

Overview

First of all, we need to clarify a concept: AOT in .NET is native. What does that mean? In other words, the code compiled by the ILC compiler (AOT compiler, refer to: .Net 7 new compiler ILC brief analysis ) is a binary code that can be run directly on each platform. For example, MacOS binaries, Linux binaries, etc. So call it native.

After the C# source code is compiled by ILC, an executable file with completely original code is generated. There is no need for JIT to compile anything at execution time because JIT is already fully utilized in ILC. In fact, AOT does not include JIT. So how is it optimized? The only time is to call JIT in ILC. So this optimization still relies on JIT. A typical is that ASP.NET applications perform well when using AOT, while also reducing the total cost.

An important goal of optimizing AOT in .NET8 is to reduce the size of the AOT executable file, regarding the effect of this. we can see it now

Create a console application below

dotnet new console -o nativeaotexample -f net7.0

Since the above was created through .NET7.0, we change the csproj of this console

<TargetFramework>net7.0</TargetFramework>改为<TargetFrameworks>net7.0;net8.0</TargetFrameworks>

You can easily build .NET7.0 or .NET8.0 programs

Continue

<PropertyGroup>...</PropertyGroup>项中添加如下<PublishAot>true</PublishAot>编译成AOT文件

Next we can publish it through dotnet publish, Linux is as follows:

dotnet publish -f net7.0 -r linux-x64 -c Release

Now it generates a .NET7.0 version of the standalone executable, the directory can be output via ls/dir to view the generated binary size

12820K /home/stoub/nativeaotexample/bin/Release/net7.0/linux-x64/publish/nativeaotexample

This is about 13M. Let’s take a look at .NET8.0

dotnet publish -f net8.0 -r linux-x64 -c Release

The size of the generated executable file is as follows:

1536K /home/stoub/nativeaotexample/bin/Release/net8.0/linux-x64/publish/nativeaotexample

With a size of 1.5M, this optimization is quite intensive. The volume has been optimized by nearly 10 times . This is the optimization magic of .NET8.0.

But the optimization is much more than that. For example, we can configure csproj to make the AOT smaller.​​​​​​​

csproj添加如下size表示要生成的AOT大小<OptimizationPreference>Size</OptimizationPreference>

If we do not need global code and data, but need specific code and data, and use immutable mode, we can add the following options to csproj

<InvariantGlobalization>true</InvariantGlobalization>

If you don’t want to throw the stack when an AOT exception occurs, you can also add the following in csproj

<StackTraceSupport>false</StackTraceSupport>

After re-releasing through dotnet publish net8.0, its size can continue to be reduced.

1248K /home/stoub/nativeaotexample/bin/Release/net8.0/linux-x64/publish/nativeaotexample

The size has been reduced by 0.3M again.

However, do you think that the optimization ends here? No, .NET8 not only improves the internals of the AOT compiler, but also performs performance optimization and improvements on individual libraries. Such as HttpClient.

Of course, in addition to volume optimization, there are other optimizations, such as avoiding auxiliary calls when reading static fields. For example, BenchmarkDotNet also supports AOT, which is support for performance testing. We can only use --runtimes nativeaot7.0 nativeaot8.0 instead of --runtimes net7.0 net8.0, as shown in the following code​​​​​​​

// dotnet run -c Release -f net7.0 --filter "*" --runtimes nativeaot7.0 nativeaot8.0
using BenchmarkDotNet.Attributes;using BenchmarkDotNet.Running;
BenchmarkSwitcher.FromAssembly(typeof(Tests).Assembly).Run(args);
[HideColumns("Error", "StdDev", "Median", "RatioSD")]public class Tests{    private static readonly int s_configValue = 42;
    [Benchmark]    public int GetConfigValue() => s_configValue;}

The above code can be run through AOT as follows

dotnet run -c Release -f net7.0 --filter "*" --runtimes nativeaot7.0 nativeaot8.0

BenchmarkDotNet output is as follows

Method Runtime Mean Ratio
GetConfigValue NativeAOT 7.0 1.1759 ns 1.000
GetConfigValue NativeAOT 8.0 0.0000 ns 0.000

It can be seen that even in the performance test Benchmark, AOT optimization is not spared.

Another thing worth mentioning is layering, because there is no concept of layering in AOT. But when compiling just in time, that is, when compiling without AOT, when a method is promoted from tier0 to tier1, the static fields in the method must be initialized. A fast path is added to AOT to check whether the field is initialized to avoid unnecessary overhead.

Some other improvements, such as the implementation of AOT locks. A hybrid approach was used, starting with lightweight spin locks and later upgrading to the System.Threading.Lock type, which should be released in .NET9.0.

 

Welcome to follow the official account (jianghupt), where the article is first published.

IntelliJ IDEA 2023.3 & JetBrains Family Bucket annual major version update new concept "defensive programming": make yourself a stable job GitHub.com runs more than 1,200 MySQL hosts, how to seamlessly upgrade to 8.0? Stephen Chow's Web3 team will launch an independent App next month. Will Firefox be eliminated? Visual Studio Code 1.85 released, floating window US CISA recommends abandoning C/C++ to eliminate memory security vulnerabilities Yu Chengdong: Huawei will launch disruptive products next year and rewrite industry history TIOBE December: C# is expected to become the programming language of the year A paper written by Lei Jun 30 years ago : "Principle and Design of Computer Virus Determination Expert System"
{{o.name}}
{{m.name}}

Guess you like

Origin my.oschina.net/u/5407571/blog/10316035