.NET 11 ランタイムの新機能

この記事では、.NET 11 の.NET ランタイムの新機能について説明します。 プレビュー 3 で最後に更新されました。

更新された最小ハードウェア要件

.NET 11 の最小ハードウェア要件が更新され、x86/x64 アーキテクチャと Arm64 アーキテクチャの両方で最新の命令セットが必要になります。 さらに、ReadyToRun (R2R) コンパイル ターゲットが更新され、新しいハードウェア機能が利用されました。

Arm64 の要件

Apple の場合、最小ハードウェアまたは ReadyToRun ターゲットに変更はありません。 Apple M1 チップは、 armv8.5-a とほぼ同等であり、少なくとも AdvSimd (NEON)、 CRCDOTPRODLSERCPCRCPC2、および RDMA 命令セットをサポートします。

Linux の場合、最小ハードウェアに変更はありません。 .NETは、AdvSimd 命令セットのサポートのみを提供する Raspberry Pi などのデバイスを引き続きサポートします。 ReadyToRun ターゲットが更新され、 LSE 命令セットが含まれるため、アプリケーションを起動すると、追加のジッティング オーバーヘッドが発生する可能性があります。

Windowsの場合、ベースラインが更新され、LSE 命令セットが必要になります。 これはWindows 11およびWindows 10に正式にサポートされているすべての Arm64 CPU に必要とされます。 さらに、Arm SBSA (サーバー ベース システム アーキテクチャ) の要件にインラインで対応しています。 ReadyToRun ターゲットは、少なくともarmv8.2-a + RCPCAdvSimdCRCLSERCPCのサポートを提供し、公式にサポートされているハードウェアの大部分をカバーする、RDMAに更新されました。

OS 以前の JIT/AOT の最小値 新しい JIT/AOT 最小値 以前の R2R ターゲット 新しい R2R ターゲット
林檎 Apple M1 (変更なし) Apple M1 (変更なし)
Linux armv8.0-a (変更なし) armv8.0-a armv8.0-a + LSE
Windows armv8.0-a armv8.0-a + LSE armv8.0-a armv8.2-a + RCPC

x86/x64 の要件

3 つのオペレーティング システム (Apple、Linux、Windows) すべてについて、ベースラインは x86-64-v1 から x86-64-v2 に更新されます。 これにより、ハードウェアは、 CMOVCX8SSE、および SSE2 の保証のみから、 CX16POPCNTSSE3SSSE3SSE4.1、および SSE4.2の保証に変更されます。 この保証は、Windows 11と、Windows 10で正式にサポートされているすべての x86/x64 CPU によって必要です。 これには、Intel と AMD によって正式にサポートされているすべてのチップが含まれており、最後の古いチップは 2013 年頃にサポート対象外になりました。

ReadyToRun ターゲットが、Windows および Linux の x86-64-v3 に更新されました。 さらに、AVXAVX2BMI1BMI2F16CFMALZCNT、および MOVBE 命令セットが含まれます。 Apple の ReadyToRun ターゲットは変更されません。

OS 以前の JIT/AOT の最小値 新しい JIT/AOT 最小値 以前の R2R ターゲット 新しい R2R ターゲット
林檎 x86-64-v1 x86-64-v2 x86-64-v2 (変更なし)
Linux x86-64-v1 x86-64-v2 x86-64-v2 x86-64-v3
Windows x86-64-v1 x86-64-v2 x86-64-v2 x86-64-v3

インパクト

.NET 11 以降では、.NETは古いハードウェアで実行できず、次のようなメッセージが出力されることがあります。

現在の CPU に 1 つ以上のベースライン命令セットがありません。

ReadyToRun 対応アセンブリの場合、一般的なデバイスの想定されるサポートを満たしていない、サポートされているハードウェアによっては、追加のスタートアップ オーバーヘッドが発生する可能性があります。

変更の理由

.NETは、多くの場合、基になるオペレーティング システムによって設定される最小ハードウェア要件を超えて、幅広いハードウェアをサポートしています。 このサポートにより、コードベースが大幅に複雑になります。特に、まだ使用される可能性が低いはるかに古いハードウェアの場合です。 さらに、AOT ターゲットが既定で設定する必要がある "最低共通分母" を定義します。これにより、一部のシナリオでパフォーマンスが低下する可能性があります。

最小ベースラインの更新は、コードベースのメンテナンスの複雑さを軽減し、基になるオペレーティング システムの文書化された (および多くの場合に適用される) ハードウェア要件に合わせて行われました。

詳細については、「更新された 最小ハードウェア要件」を参照してください。

ランタイム非同期

.NET 11 では、ランタイムネイティブの非同期 (ランタイム非同期 V2) が導入されています。これは、コンパイラによって生成された非同期ステート マシンをランタイムで管理された中断と再開に置き換える上で重要な手順です。 コンパイラがステート マシン クラスを出力する代わりに、ランタイム自体は非同期実行を追跡し、よりクリーンなスタック トレースを生成し、デバッグ性を向上させ、オーバーヘッドを削減します。

ランタイム非同期はプレビュー機能です。 オプトインするには、次のプロパティをプロジェクト ファイルに追加します。

<PropertyGroup>
  <Features>runtime-async=on</Features>
</PropertyGroup>

Preview 3 以降では、 net11.0 プロジェクトでランタイム非同期を使用する <EnablePreviewFeatures>true</EnablePreviewFeatures> は不要になりました。

よりクリーンなライブ スタック トレース

最も目に見える改善点は、実行中にプロファイラー、デバッガー、に表示されるnew StackTrace()です。 コンパイラによって生成された非同期では、各非同期メソッドによってステート マシン インフラストラクチャから複数のフレームが生成されます。 ランタイム非同期では、実際のメソッドは呼び出し履歴に直接表示されます。

// To enable runtime async, add the following to your .csproj:
//   <Features>runtime-async=on</Features>

await OuterAsync();

static async Task OuterAsync()
{
    await Task.CompletedTask;
    await MiddleAsync();
}

static async Task MiddleAsync()
{
    await Task.CompletedTask;
    await InnerAsync();
}

static async Task InnerAsync()
{
    await Task.CompletedTask;
    Console.WriteLine(new StackTrace(fNeedFileInfo: true));
}

runtime-async なし —13 フレーム、ステートマシン インフラストラクチャが表示されます。

   at Program.<<Main>$>g__InnerAsync|0_2() in Program.cs:line 24
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](...)
   at Program.<<Main>$>g__InnerAsync|0_2()
   at Program.<<Main>$>g__MiddleAsync|0_1() in Program.cs:line 14
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](...)
   at Program.<<Main>$>g__MiddleAsync|0_1()
   at Program.<<Main>$>g__OuterAsync|0_0() in Program.cs:line 8
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](...)
   at Program.<<Main>$>g__OuterAsync|0_0()
   at Program.<Main>$(String[] args) in Program.cs:line 3
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](...)
   at Program.<Main>$(String[] args)
   at Program.<Main>(String[] args)

runtime-async—5 フレームの場合、実際のコール チェーンは次のようになります。

   at Program.<<Main>$>g__InnerAsync|0_2() in Program.cs:line 24
   at Program.<<Main>$>g__MiddleAsync|0_1() in Program.cs:line 14
   at Program.<<Main>$>g__OuterAsync|0_0() in Program.cs:line 8
   at Program.<Main>$(String[] args) in Program.cs:line 3
   at Program.<Main>(String[] args)

コンパイラによって生成されたコード内の既存のcatch (Exception ex)クリーンアップによってそのケースが処理されるため、(ExceptionDispatchInfoからの) 例外スタック トレースは、ランタイム非同期の有無に関係なく、既に同じように見えます。 改善点は、ライブ実行中 表示される内容です。

この改善は、プロファイリング ツール、診断ログ、デバッガー呼び出し履歴ウィンドウなど、ライブ実行スタックを検査するあらゆる利点があります。

NativeAOT と ReadyToRun のサポート

プレビュー 3 では、NativeAOT および ReadyToRun コンパイルのランタイム非同期サポートが追加されました。 これにより、JIT コンパイル コードを超えて、事前コンパイル済みのシナリオまで機能が拡張されます。 また、ランタイムは継続オブジェクトをより積極的に再利用し、変更されていないローカルの保存を回避し、非同期の負荷の高いコードでの割り当て負荷を軽減します。

デバッグの機能強化

ブレークポイントがランタイム非同期メソッド内で正しくバインドされるようになりました。デバッガーは、コンパイラによって生成されたインフラストラクチャにジャンプすることなく、 await 境界をステップ 実行できるようになりました。

JIT の機能強化

  • 境界チェックの削除: Just-In-Time (JIT) コンパイラでは、インデックスと定数が長さ ( i + cns < lenなど) と比較される共通パターンの境界チェックが不要になりました。 また、インデックスの from-end アクセスの冗長境界チェックも不要になります (たとえば、 values[^1])。 これらの機能強化により、タイト ループの冗長チェックが削減され、配列操作とスパン操作のスループットが向上します。
  • 冗長なチェック されたコンテキストの削除: JIT は、冗長なチェック済み算術コンテキスト (たとえば、値が既に範囲内であることが判明している場合) を証明および削除できるようになりました。 この最適化により、生成されたコードで不要なオーバーフロー チェックが不要になります。
  • スイッチ式の折りたたみ: 複数ターゲットの switch 式は、ターゲットが定数の小さなセット (たとえば、 x is 0 or 1 or 2 or 3 or 4) である場合に、より単純な分岐なしのチェックに分割されるようになりました。
  • uint を float/double にキャストする処理の高速化: AVX-512 より前の x86 ハードウェアでは、uintfloat または double にキャストする処理が高速です。
  • ReadyToRun イメージでの非仮想化: ReadyToRun (R2R) イメージで、共有されていない汎用仮想メソッド呼び出しを非仮想化できるようになりました。これにより、ジェネリック シナリオ用の事前コンパイル済みコードのパフォーマンスが向上します。
  • SVE2 組み込み関数: 新しい Arm SVE2 (スケーラブル ベクター拡張 2) 組み込み関数を使用できます: ShiftRightLogicalNarrowingSaturate(Even|Odd)。 これにより、SVE2 をサポートする Arm ハードウェアで使用できるベクター化された操作のセットが拡張されます。

VM の機能強化

  • JIT 以外のプラットフォームでのキャッシュインターフェイスディスパッチ: iOS などの JIT サポートがないプラットフォームでは、インターフェイスディスパッチは高価な汎用修正パスにフォールバックしていました。 キャッシュされたディスパッチにより、これらのターゲットのインターフェイス負荷の高いコードが最大 200 倍向上します。
  • Linux 上のGuid.NewGuid():Linux 上のGuid.NewGuid()では、getrandom()から読み取る代わりにバッチ キャッシュを使用する/dev/urandom syscall が使用されるようになりました。これにより、GUID 生成のスループットが約 12% 向上します。

WebAssembly の機能強化

プレビュー 3 では、ブラウザーと WebAssembly のサポートが拡張され、いくつかの機能強化が行われました。

  • WebCIL ペイロードの読み込み: ランタイムで WebCIL ペイロードを直接読み込むことができるようになり、ブラウザーベースのデプロイ シナリオとの互換性が向上しました。
  • Better デバッグ シンボル: WebAssembly デバッグのシンボルとスタック トレースの品質が向上し、ブラウザーでホストされる.NET アプリの問題を簡単に診断できるようになりました。
  • float[]Span<float>、および ArraySegment<float> マーシャリング:float[]Span<float>、および ArraySegment<float> が JavaScript の境界を越えて直接マーシャリングされるようになりました。これにより、相互運用が多いコードのオーバーヘッドが削減されます。

こちらも参照ください