はじめに
これは、Visual Basic Advent Calendar 2021の9日目の記事となります。
別のところで C#用のNativeAOTの記事を書きました。Qiitaでは2つのAdvent Calendarのリンクができません。
NativeAOTとは
ILを完全にネイティブコードに落とし込むオープンソースプロジェクトです。ILからの変換なので、C#以外にVisual BasicやF#でも可能です。
2021年以降は、NativeAOTリポジトリ
https://github.com/dotnet/runtimelab/tree/feature/NativeAOT
※将来的にはメインプロジェクトに統合される予定
NativeAOTの準備
C++ ビルドツール
マイクロソフト C++ ビルドツールのインストール
https://visualstudio.microsoft.com/ja/visual-cpp-build-tools/
C++ によるデスクトップ開発
Visual Studio Installerを起動します。変更ボタンをクリックします。
ワークロードタブに「C++によるデスクトップ開発」にチェックを入れ、「インストール」をクリックします。
ビルド
HelloWorldのサンプルが下記サイトにあります。
https://github.com/dotnet/runtimelab/tree/feature/NativeAOT/samples/HelloWorld
HelloWorldのままではつまらないので、今回はフィボナッチ数列にしてみます。
プロジェクト生成
NativeAOTは、ILからの変換なので、C#以外にVisual BasicやF#でも可能です。
今回は、Visual Basicを指定します。
自分はC:\WorkSpaceフォルダにいつもプロジェクトを生成しています。
> cd C:\WorkSpace
> dotnet new console -o FibonacciVB -lang VB
> cd FibonacciVB
プロジェクトにNativeAOTを追加
> dotnet new nugetconfig
テンプレート "NuGet Config" が正常に作成されました。
nuget.configファイルが追加されます。
nuget.configの編集
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<!--To inherit the global NuGet package sources remove the <clear/> line below -->
<clear />
<add key="nuget" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>
packageSources要素のより下を下記の置き換えます。
<add key="dotnet-experimental" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
最終結果
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<!--To inherit the global NuGet package sources remove the <clear/> line below -->
<clear />
<add key="dotnet-experimental" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
</packageSources>
</configuration>
ILCompilerの追加
> dotnet add package Microsoft.DotNet.ILCompiler -v 7.0.0-*
FibonacciVB.vbproj にItemGroup要素にMicrosoft.DotNet.ILCompiler
が追加されました。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<RootNamespace>FibonacciVB</RootNamespace>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.DotNet.ILCompiler" Version="7.0.0-*" />
</ItemGroup>
</Project>
プログラムの修正
HelloWorldのままなのでプログラムを書き換えます。
Imports System
Module Program
Sub Main(args As String())
Console.WriteLine("Hello World!")
End Sub
End Module
フィボナッチ数列にプログラムを書き換えます。
※C# 10.0 のように、namespace、Main、Import Systemを記述しないスクリプトライクのようには記述できません。
Imports System
Module VBModule
Sub Main(args As String())
Fibonacci_Iterative(10)
End Sub
Sub Fibonacci_Iterative(ByVal len As Integer)
Dim a, b, c As Integer
a = 0 : b = 1 : c = 0
Console.Write("{0} {1}", a, b)
For i As Integer = 2 To len - 1
c = a + b
Console.Write(" {0}", c)
a = b
b = c
Next
End Sub
End Module
NativeAOTによる発行
下記は発行のコマンドの説明になります。
> dotnet publish -r <RID> -c <Configuration>
RIDには、win-x64
or linux-x64
or osx-x64
を指定します。
残念ながらlinux-arm64
がまだありませんので、Raspberry Pi 4用には出来ない。
Configurationには、 debug
or release
を指定します。
では、win-x64のreleaseで発行します。
> dotnet publish -r win-x64 -c release
FibonacciVB -> C:\WorkSpace\FibonacciVB\bin\release\net6.0\win-x64\publish\
FibonacciVB.exe でサイズが 4.18MiB あります。自分はWindows Terminal上でPowershellを使用しているので、先頭に"./"を付けて実行します。
> cd bin\release\net6.0\win-x64\native
> ./FibonacciVB.exe
0 1 1 2 3 5 8 13 21 34
イメージサイズ縮小
現在、FibonacciVB.exe でサイズが 4.18MiB あります。
PropertyGroup要素に幾つかオプションを追加してみます。
今回は実行速度より小さいコードサイズを優先にします。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<RootNamespace>FibonacciVB</RootNamespace>
<TargetFramework>net6.0</TargetFramework>
<!-- https://github.com/dotnet/corert/blob/master/Documentation/using-corert/optimizing-corert.md -->
<IlcOptimizationPreference>Size</IlcOptimizationPreference>
<RootAllApplicationAssemblies>false</RootAllApplicationAssemblies>
<IlcGenerateCompleteTypeMetadata>false</IlcGenerateCompleteTypeMetadata>
<IlcGenerateStackTraceData>false</IlcGenerateStackTraceData>
<IlcDisableReflection>true</IlcDisableReflection>
<IlcFoldIdenticalMethodBodies>true</IlcFoldIdenticalMethodBodies>
<IlcDisableUnhandledExceptionExperience>false</IlcDisableUnhandledExceptionExperience>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.DotNet.ILCompiler" Version="7.0.0-*" />
</ItemGroup>
</Project>
再発行
> cd C:\WorkSpace\FibonacciVB
> dotnet publish -r win-x64 -c release
FibonacciVB.exe のサイズが 4.18 MiB → 1.13 MiB まで小さくなりました。
実行結果も問題ありません。
> cd bin\release\net6.0\win-x64\native
> ./FibonacciVB.exe
0 1 1 2 3 5 8 13 21 34
究極に小さく
下記サイトではあらゆるテクニックを駆使して、64MiBから8176バイトまで小さくしています。
Building a self-contained game in C# under 8 kilobytes
bflatの紹介
単純なアプリケーションの場合は bflat を使用すると良い結果が得られる可能性があります。
How to compile a .NET application to native code? - stackoverflow
bflatは、.NET実行可能ファイルを生成する「公式」C#コンパイラである。最も単純なアプリであっても、サイズが2MBから3MBの実行可能ファイルを生成します。
https://github.com/MichalStrehovsky/bflat
その他
Redditにて、NativeAOTの.NET 7での計画のスレッドがあります。
NativeAOT .NET 7 Plans
Is Native AOT on the roadmap?
WinFormsとNativeAOTについて、.NET 5の時なので.NET 6で対応されているかも。
Again WinForms and NativeAOT
最後に
無事に、.NET 6 + VB でも NativeAOTによりネイティブプログラムにすることが出来ました。