LoginSignup
12
17

More than 1 year has passed since last update.

C# / .NET 7 でlinuxのネイティブコード出力(NativeAOT)

Last updated at Posted at 2022-09-07

毎年恒例 Stephen Toub さんの Performance Improvements in .NET 7 が8月末に投稿されました。

毎年.NETの改善状況を見るのを楽しみにしているのですが、今回はC#erならだれもが夢見る、C#でネイティブコード出力する機能、 NativeAOT がとても気になります。
記事ではWindowsの例が書いてありますが linuxで試してみたい と思います。

Native AoTとは

Native AOTはCPUネイティブコード(機械語)を直接出力する機能です。
@yajuさんの「dotnet-6.0におけるNativeAOTについて」がとても詳しいのでそちらを是非みてください。

ネイティブ化によって

  • パフォーマンス向上
  • バイナリーサイズの縮小
  • dotnet未インストールマシンでの実行

などの利点が見込めます。例えばラズパイや小さな組み込みマシンでdotnetランタイムをインストールしなくともプログラムが動くとなればかなりのメリットになります。

現在はコンソールアプリのみ等の制約があり、まだまだ改善されていく機能だと思いますが今後の発展がとても楽しみです。

dotnet7.0-preview7 のインストール

ここは本題ではないので簡単に。
今回はRHEL9互換の AlmaLinux 9 を最小インストールした状態から始めます。最小だとtarコマンドすらないのでそこからです。

$ sudo dnf install tar gzip libicu
$ curl -O https://download.visualstudio.microsoft.com/download/pr/aabf15d3-f201-4a6c-9a7e-def050d054af/0a8eba2d8abcf1c28605744f3a48252f/dotnet-sdk-7.0.100-preview.7.22377.5-linux-x64.tar.gz
$ mkdir -p $HOME/dotnet && tar zxf dotnet-sdk-7.0.100-preview.7.22377.5-linux-x64.tar.gz -C $HOME/dotnet
$ export DOTNET_ROOT=$HOME/dotnet
$ export PATH=$PATH:$HOME/dotnet
$ dotnet --version
7.0.100-preview.7.22377.5

exportの2行は .bashrc にでも書いておきましょう。

Hello, World

パフォーマンスを見たかったのですが今回は出力バイナリを見ることにします。「Hello,World」でいきます。

$ dotnet new console -o con1
con1/Program.cs
Console.WriteLine("Hello, World!");

ILコード出力

まずは普通にIL版。シングルファイルにしてみます。

単一ファイル
$ dotnet publish --os linux -c release -o ~/bin/ -p:PublishSingleFile=true
$ ls -l ~/bin
-rwxr-xr-x.  186058  con1

ライブラリ依存で186KB

ランタイム内包
$ dotnet publish --os linux -c release -o ~/bin/ \
    --self-contained=true \
    -p:PublishSingleFile=true \
    -p:PublishReadyToRun=true \
    -p:PublishTrimmed=true
$ ls -l ~/bin
-rwxr-xr-x. 1 16697946  con1

dotnetランタイムをすべて内包した場合は16MBになりました。
Hello,World で 16MB は大きいですね・・。

Native AOTを使えるようにする

ここからが本番。記事を読む限りcsprojファイルに <PublishAot>true</PublishAot> を追記することでコンパイル出来るように見えます。

cat con1.csproj
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net7.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <PublishAot>true</PublishAot>
  </PropertyGroup>
</Project>

これでdotnet publish を実行してみると以下のようにエラーが出ます。

error : Platform linker ('clang' or 'gcc') not found in PATH. Try installing appropriate package for clang or gcc to resolve the problem. [/home/xxx/con1/con1.csproj]

ということで足りないツール、ライブラリを追加でインストールします。
必要なものはgcc, g++, zlibです。

$ sudo dnf install gcc gcc-c++ zlib-devel

Ubuntuなら sudo apt-get install clang zlib1g-dev でしょうか(公式情報)。
これでネイティブコンパイルできるようになります。出来上がったバイナリーサイズを見てみます。

$ dotnet publish -r linux-x64 -c release -o ~/bin2/
$ ls ~/bin2/
-rwxr-xr-x. 1  15609816  con1

15MB。ちょっと大きいですね。

バイナリーを小さくする。

Performance Improvements in .NET 7 ではWindowsのバイナリーは3MBなのでもっと小さくなる余地はあるはずです。この記事にある以下のコンパイルオプションを適用して再度確認してみます。

  • InvariantGlobalization = true カルチャー情報を無視
  • UseSystemResourceKeys = true 例外メッセージを除去
  • IlcGenerateStackTraceData = false スタックトレース情報を最低限に
  • DebuggerSupport = false デバッガサポートなし

その他今回は関係なさそうなSerializerやHttp関連も含めて記述してみました。

con1.csprojに追記
    <InvariantGlobalization>true</InvariantGlobalization>
    <UseSystemResourceKeys>true</UseSystemResourceKeys>
    <IlcOptimizationPreference>Size</IlcOptimizationPreference>
    <IlcGenerateStackTraceData>false</IlcGenerateStackTraceData>
    <DebuggerSupport>false</DebuggerSupport>
    <EnableUnsafeBinaryFormatterSerialization>false</EnableUnsafeBinaryFormatterSerialization>
    <EventSourceSupport>false</EventSourceSupport>
    <HttpActivityPropagationSupport>false</HttpActivityPropagationSupport>
    <MetadataUpdaterSupport>false</MetadataUpdaterSupport>

11MB まで削減できました。

$ dotnet publish -r linux-x64 -c release -o ~/bin3/
$ ls -l ~/bin3/con1
-rwxr-xr-x. 11199792  con1

refrection 関連を除去

今回のコードではrefrectionは使っていないので このドキュメントを参考にしてrefrection関連のコードを落とします。

csprojへの追記
<RootAllApplicationAssemblies>false</RootAllApplicationAssemblies>
<IlcGenerateCompleteTypeMetadata>false</IlcGenerateCompleteTypeMetadata>
<IlcDisableReflection>true</IlcDisableReflection>
<IlcOptimizationPreference>Size</IlcOptimizationPreference>
<IlcFoldIdenticalMethodBodies>true</IlcFoldIdenticalMethodBodies>

これで6MBまで減りました。

$ ls -l ~/bin4/
-rwxr-xr-x. 1 6233888  con1

埋め込みデバッグ情報を除去

バイナリーを覗いてみるとデバッグ用文字列が入っています。

$ strings ~/bin4/con1
...
S_P_CoreLib_System_Buffers_SpanAction_2<Char__S_P_CoreLib_System_ValueTuple_3<IntPtr__Int32__S_P_CoreLib_System_HexConverter_Casing>>
S_P_CoreLib_System_ValueTuple_2<System___Canon__Int32>
S_P_CoreLib_System_Buffers_SpanAction_2<Char__S_P_CoreLib_System_ValueTuple_2<System___Canon__Int32>>
S_P_CoreLib_System_ValueTuple_3<System___Canon__System___Canon__Int32>
S_P_CoreLib_System_Buffers_SpanAction_2<Char__S_P_CoreLib_System_ValueTuple_3<System___Canon__System___Canon__Int32>>
...

公式ドキュメントでも「MacやLinuxではデバッグ情報がデフォルトで入るのでstripで削ってね」と書いてあるので除去してみます。

$ cp con1 con2
$ strip con2
$ ls -l
-rwxr-xr-x. 6233888  con1
-rwxr-xr-x. 1428120  con2

1.4MB まで減りました。

2022年9月10日追記

linux/mac用にcsprojのオプション <StripSymbols>true</StripSymbols> がありました。これを使うとstripコマンドと同じ効果があり、デバッグシンボルを *.dbgとして別ファイルに出力してくれます。

出力バイナリーの確認と考察

出来上がったバイナリーは以下の依存関係を持っています。

$ ldd ~/bin3/con1
    linux-vdso.so.1        // linuxシステムコール用ライブラリ
    libstdc++.so.6         // GNU C++ 標準ライブラリ
    libm.so.6              // 数学系の標準ライブラリ
    libc.so.6              // Linux Cライブラリ
    ld-linux-x86-64.so.2   // ELF形式ファイルのローダー
    libgcc_s.so.1          // gccのライブラリ

どんなコードが出力されているのかまでは追ってません。

コンパイルの様子を見るとcppやcファイルは出力されず、DLLが一度出力されています。
ILコードを何らかの形でネイティブにしていると思われますが、UnityのIL2CPPのようにcppにするのではなく、JITのコンパイル結果と同じようなものなのかもしれません。その辺はいつか逆アセンブリして確かめてみたいです。

また、「Hello,World」が 1.4MB は納得できない人が多いかもしれません。
が、Console.WriteLine()はStream 実装が残るでしょうし、文字列を使うため string の実装が、さらにGC(ガベージコレクション)も入っているのでしょう。まずはしょうがないのかなと思います。

参考

公式ドキュメント(GitHub)
https://github.com/dotnet/runtime/tree/main/src/coreclr/nativeaot/docs

Optimizing programs targeting Native AOT
https://github.com/dotnet/runtime/blob/main/src/coreclr/nativeaot/docs/optimizing.md

12
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
17