世界を動かす技術を、日本語で。

.NET 11におけるパフォーマンス向上

概要

  • Christopher Guest はモキュメンタリー映画の先駆者として知られる存在
  • 代表作 This Is Spinal Tap の有名な「11まで上がる」アンプのジョークを.NET 11と重ねて紹介
  • .NET 11 は実際に「1段上のパフォーマンス」を実現するアップデート
  • ベンチマーク手法やセットアップ方法を具体的に解説
  • JITコンパイラ やデアブストラクションなど、.NET 11の内部的な最適化ポイントを詳細に説明

.NET 11は「1段上」:Spinal Tapのジョークとパフォーマンス進化

  • モキュメンタリー というジャンルを世に広めたChristopher Guestの功績
    • 代表作 Waiting for GuffmanBest in Show、そして伝説的な This Is Spinal Tap の存在感
  • This Is Spinal Tap の「11まで上がるアンプ」の名場面
    • Nigelが「普通は10までだが、これは11まで上がる」と誇示するジョーク
    • .NET 11のアップデートを「実際に1段上」と重ねて紹介
  • .NET 11は、 パフォーマンス面で本当に「1段上」 を実現
    • 不要なバウンドチェックや割り当ての削減
    • ロックの回避やループサイクルの短縮
    • 定数畳み込みや冗長なチェックのループ外移動
    • 命令融合、システムコールの回避、SIMDによる配列コピーなど
  • 積み重ねによる本物のパフォーマンス向上 の重要性
    • 小さな最適化の連鎖が、全体として大きな成果に

ベンチマーク環境のセットアップ

  • BenchmarkDotNet を活用したマイクロベンチマークの実施方法
    • .NET 10と.NET 11の両方をインストールし、比較用の新規コンソールプロジェクトを作成
    • benchmarks.csprojを編集して、2つのターゲットフレームワークを指定
    • 必要なパッケージ参照(BenchmarkDotNet、System.IO.Hashingなど)を追加
  • ベンチマークの実行手順
    • Program.csに対象ベンチマークコードをコピー
    • dotnet run -c Release -f net10.0 --filter "*" --runtimes net10.0 net11.0で両バージョン比較
    • 1つのランタイムのみで比較する場合はdotnet run -c Release -f net11.0 --filter "*"
  • 注意点
    • マイクロベンチマークはハードウェアやOS、環境によって結果が変わる
    • あくまで参考値として評価

JIT(Just-In-Timeコンパイラ)の進化

  • JITコンパイラ は.NETパフォーマンス向上の要
    • C#やF#、VBはまずIL(中間言語)にコンパイルされ、JITがCPU命令へ変換
    • JITの最適化により、アプリやライブラリ全体に恩恵
  • デアブストラクション(deabstraction) とは
    • 開発者が書いた抽象化(インターフェースや仮想メソッド)を、実行時に無駄なく最適化
    • 例:インターフェース経由の呼び出しを具体的な型に置き換えてインライン化
  • 仮想呼び出しの最適化
    • JITは「どの具象型が呼ばれるか」を静的解析やPGO(プロファイルガイド最適化)で推測
      • 例:Animalクラスを継承するDogのみが実装されている場合、直接Dog.Speak()を呼び出せる
    • インライン化により、関数呼び出しのオーバーヘッド削減+さらなる最適化が可能
  • PGO(プロファイルガイド最適化) の仕組み
    • 最初は簡易的なTier 0コンパイルで実行し、実際の動作をプロファイリング
    • 十分なデータが集まるとTier 1で最適化コンパイルを実施
    • 動的な型情報や分岐情報を活用し、仮想呼び出しのインライン化など高度な最適化を実現

.NET 11のパフォーマンス向上ポイントまとめ

  • 小さな最適化 (バウンドチェック削除、割り当て削減、ロック回避など)の積み重ね
  • JITの進化 による仮想呼び出しのインライン化とデアブストラクション
  • PGO活用 による現実的なコードパスの高速化
  • ベンチマーク環境の整備 で、ユーザー自身が改善効果を確認可能

これらの進化により、.NET 11は「本当に1段上」のパフォーマンスを実現。まさに「One louder」なフレームワークへ進化。

Hackerたちの意見

Runtime asyncは確かに面白い進展だね。これがどうなるのか、すごく楽しみ!

スパイナル・タップの「11にする」。俺のノートパソコン(Kubuntu)は、音量を100%を超えて150%まで上げられるコントロールがあるんだ。15まで上げられるぜ! ||/ (公式のASCIIアートの四本指の悪魔の角ってあるのかな)

\m/

音質が100%を超えると劣化し始めるのに気づいたよ(少なくともメディアの音量がすでに高い場合)。これは本当に便利な概念だね。どのLinuxディストリビューションから思い出してるのかはわからないけど、大学の古いネットブックで役立ったことがある。

バカな質問だけど:システム言語じゃない開発者はアセンブリを知っておくべき?;Arm64 --- .NET 10 +++ .NET 11 @@ -13,8 +13,6 @@ ble G_M000_IG04 G_M000_IG03: - cmp w1, w2 - bhs G_M000_IG05 str wzr, [x0, w1, UXTW #2] G_M000_IG04: @@ -25,4 +23,4 @@ bl CORINFO_HELP_RNGCHKFAIL brk #0 -; コードの合計バイト数 68 +; コードの合計バイト数 60 C#/F#はそこそこ得意だけど、上のコードが何を言ってるのかを本を引っ張り出して学ぶ理由ってあるの?

いいえ。もし低レベルのC言語プログラマーなら、役に立つよ。

知っておく必要はないけど、コードを本当に最適化したいなら、ターゲットCPUでどうなるかを理解するのはすごく助けになるよ。特に特定の命令の影響(メモリアクセスのコスト、分岐予測のミスの可能性など)を知っているとね。三文字のニーモニックは、アーキテクチャを知らなくても結構簡単に解読できるよ。「B」で始まるものは分岐だから、「ble」は「小さいか等しい場合に分岐」って意味。LとSのニーモニックは、メモリからのロードとストアだね。その後はスタックとレジスタを理解すれば、ほぼ大丈夫。アセンブリでは、メモリから何かをレジスタにロードして、そのレジスタで基本的な処理(加算や除算など)をして、結果をメモリに戻すか、別のメモリの場所にある命令にジャンプするための決定をするんだ。ほとんどの開発者はこの知識を必要としないけど、何もできないコンピュータ(8ビットコンピュータ、2MHzプロセッサ、32KBのRAM、そのうち20KBが画面用)で育った俺にとっては、これを知っておくことは必須だった。でも、今でもC#の仕事をしていてもこの知識は役立ってるよ。俺はちょっとパフォーマンス調整オタクだからね… [*] それ以上もあるかも

そうでもないよ、アクセスできるってことだけ知ってればいい。いつか画像や動画を処理してて遅いなって感じることがあったら、こういうレベルに降りてくるのが役立つかもしれない。でも、最初に見るべきことではないかな。

まあね。Common Lispのdisassemble関数を結構使うけど、逆にして関数が何をしてるかを説明するのは難しい。でも、どの関数がインライン化されてるか、メモリにある値とレジスタにある値の違い、ボックス化やアンボックス化が多いかどうか、SIMDが使われてるかどうかは見れるんだ。もっと重要なのは、2つのバージョンの関数を比較して、どっちが基準に合ってるかを見ることができること。完璧ではないけど、俺の経験では最適化の指針としてはすごく役立つよ。ちなみに、Jeff Duntemannの「Assembly Language: step-by-step」を読んで、昔アセンブリで三目並べのゲームを書いたことがあるから、構文を理解するのにちょっと役立ってる。(技術的にはパッチファイルだけどね。)

アセンブリは正直、特にARMアセンブリの基本を学ぶのはすごく簡単だよ(全プログラムのアセンブリはまだ難しいけど)。最悪の場合でも、何か面白いことを学べるよ。

C#は色んな役割を持ってるよね。もし高レベルなことだけやってるなら、例えば99%の時間ユーザーを待ってるローカルGUIアプリだけ作ってるなら、理解する必要はないかも。でもC#をもっと低レベルなシステム言語として扱うこともできるし、サーバーで数値計算を最適化する必要があるなら、読む力が役立つかもね。

印象的な技術的な仕事と著作だね。文脈的に重要な疑問は、AoTはどうなったの?

Hacker Newsで議論の続きを見る