LoginSignup
7

More than 1 year has passed since last update.

posted at

updated at

dotnet-5.0におけるNativeAOTについて

始めに

dotnet-5.0におけるNativeAOTについて、どういうものかという概要と、バイナリを生成するところまで書きたいと思う。

AOT(Ahead Of Time)コンパイルとは

通常dotnetのアセンブリは、ソースからILと呼ばれる中間言語に変換された形でいったん出力され、実行時にコンパイラによって、ネイティブコードに変換されて処理が実行される。これをJIT(Just In Time)コンパイルという
この変換処理を予め行い、ネイティブコードに落とし込んだバイナリを生成するのがAOT(Ahead Of Time)コンパイルとなる。

特に新しい概念というわけではなく、C/C++やrust,golang等はAOTと言えると思う。

dotnetでAOTを導入する目的

そもそもdotnetではILからのJITが基本だが、それでもAOTが検討されるのは、以下の理由による。

起動時間を含む積極的な最適化

JITでは、実行時に最適化をかけるという仕組み上、起動時に著しく時間がかかるような最適化を行うことができない。
対してAOTでは、ビルド時に最適化をかけることが可能なため、時間的な制約からはある程度解放される(実用上限度はあるが)。
そのため、より深いコード解析を行うことにより、より積極的な最適化が可能になる。
また、JITという工程を飛ばすことにより、起動時間の短縮が可能となる。
起動時間の短縮というだけならば、ReadyToRunという既存のAOTで実現可能。ただし、後の最適化のためにILは温存されるので、生成物のサイズ自体は増える。

ただし、JITでしかできない最適化というものはあるので、最適化でどちらが良いかというのは一概に言えるものではないというのが難しい所ではある。
また、dotnet系言語は強力なリフレクションや動的コード生成を前提にした機能や手法などが数多く存在するが、AOTとは相性が悪いため、かなり制限を受けるというのもデメリットとはいえる。

サイズの削減

ビルド時に依存関係を解決することにより、不要なコードを積極的に行い、最終的な生成物のサイズを軽減できる。
大抵のアプリケーションは、ベースライブラリの全ての機能を使用しているわけではないため、この手法により、大幅なサイズ削減が見込める。

ただし、基本的にILよりもネイティブコードの方がサイズが大きくなりがちなのと、C#の基礎的な機能(例外とか)を実現するためのIL→ネイティブコードの変換量がかなり大きくなるようなプラットフォーム(iOSとかWASMとか)では、却って生成物のサイズが大きくなる場合もある。

また、リフレクションを多用するようなプロジェクトでは、リフレクション用のメタ情報を持たせる必要があるため、結局コード削減量よりメタ情報追加による増加の方が勝ってしまい、生成されるもののサイズは大きくなってしまう場合がある。

プラットフォームの制限

例えばiOS等は動的コードの実行を禁止しているため、JITがそもそも行えず、AOTを行うしかないという事情がある。

NativeAOTとは

ILを完全にネイティブコードに落とし込むプロジェクトで、以前はcorertと呼ばれていた。
実はCarrionというゲームでcorert+monogameの組み合わせで使われているらしいが、あくまでcorert自体は実験的プロジェクトで、実用する際は注意が必要なものだった。

それが、最近dotnet/runtimelabという、ランタイム自体に関する実験的修正を試みるプロジェクトに移って開発されることとなった。runtimelabに移ったことで、将来的にどのようにメインプロジェクトに統合されるかというのが具体的に視野に入るようになったので、前進はしていると思う。

現在の進捗を確認したい場合は、個別のブランチに移って履歴を確認することができる

NativeAOTの特徴

NativeAOTは、以下のような特徴を持つ

メリットデメリットがかなり激しいので、採用する場合はよく考えて採用した方が良い。

NativeAOTを体験する

現状これらの制約を踏まえた上でNativeAOTを行いたい場合、ソースからビルドするというのがまず推奨されるやり方だが、
そこまでするのはきついという人も多いと思うので、ビルド済みバイナリを利用して、nuget経由で比較的手軽にできる方法を紹介する。ただし、dotnetにおけるNativeAOTはあくまでも発展途上で、大胆な仕様変更や不可解なエラーも十分あり得るため、取り扱いには細心の注意を払う事

事前準備

事前に必要なものに関しては、 https://github.com/dotnet/runtimelab/blob/feature/NativeAOT/samples/prerequisites.md に記述がある。
上記に加えて、dotnet-sdk 5.0以降が必要。

また、一部のlinux,mac環境では、clangコンパイラが別名表記になっている場合がある(clang-6.0とか後ろにバージョン表記が入る等)。
そのような環境でビルドする際は、msbuildのプロパティにCppCompilerAndLinker=[clangのパス]を設定する必要がある。

プロジェクトの作成からバイナリの生成まで

  1. dotnet new等で、通常のアプリケーションプロジェクトを作成する
    • TargetFrameworkは"net5.0"以降、あるいは"netcoreapp*"にする
  2. csprojがあるディレクトリに、dotnet new nugetconfig等でnuget.configを作成する
  3. nuget.configに https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json をソースとして追加する
  4. Microsoft.DotNet.ILCompilerのバージョン6.0.0-*をnuget参照に追加する
  5. dotnet publish -r [RID] -c [Configuration]を実行する

以上を実行すると、bin\[Configuration]\[TargetFramework]\[RID]\native 以下に実行可能バイナリが生成される。
現状特に設定を追加せずに行うと、HelloWorldレベルのもので大体7MB程度になった。
今回は詳しく検証していないが、ビルドオプションの工夫で、もっと減らせる余地もあるらしい。ビルドオプション等は以下。
https://github.com/dotnet/runtimelab/blob/feature/NativeAOT/docs/using-nativeaot/optimizing.md

今回は試してないが、ネイティブライブラリの作成も可能らしい。

終わりに

NativeAOTについて、とりあえず入り口となる部分を書いたが、今後も仕様変更等は当然起こり得る事なので、適度に動向を追っていきたい。
後、この手の技術については、dotnetだけではなく他でも色々研究されている分野なので、それらと比較していってもいいかもしれない。

参考リンク

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
What you can do with signing up
7