$30 off During Our Annual Pro Sale. View Details »

.NET 8 で既定で有効になった Dynamic PGO について

neno
October 28, 2023

.NET 8 で既定で有効になった Dynamic PGO について

.NET ラボ 2023/10/28 での発表資料

ブログ
https://blog.neno.dev/entry/2023/10/29/131336

neno

October 28, 2023
Tweet

More Decks by neno

Other Decks in Technology

Transcript

  1. .NET 8 で既定で有効になった
    Dynamic PGO について
    .NET ラボ 2023/10/28
    何縫ねの。

    View Slide

  2. 自己紹介
    1
    • 所属: NTTコミュニケーションズ
    イノベーションセンター
    • 趣味: C#, OSS, ドール, 一眼(α7 IV)
    • 執心領域
    • C# ⇔ TypeScript
    • SignalR
    何縫ねの。
    nenoNaninu
    nenoMake
    ブログ https://blog.neno.dev
    その他 https://neno.dev

    View Slide

  3. OSS 紹介
    2
    属性を付与するだけ
    Tapper
    • C# の型定義から TypeScript の型定義を生成する .NET Tool/ library
    • JSON / MessagePack 対応!
    https://github.com/nenoNaninu/Tapper

    View Slide

  4. OSS 紹介
    3
    • C# の SignalR Client を強く型付けするための Source Generator
    TypedSignalR.Client
    Before
    After (using TypedSignalR.Client)
    こんな SignalR の
    Hub と Receiver の interface が
    あったとして…
    脱文字列!
    全てが強く型付け!
    https://github.com/nenoNaninu/TypedSignalR.Client

    View Slide

  5. 4
    • TypeScript の SignalR Client を強く型付けするための .NET Tool / library
    TypedSignalR.Client.TypeScript
    Before
    After (using TypedSignalR.Client.TypeScript)
    脱文字列!
    全てが強く型付け!
    TypeScript 用の型を
    C# から自動生成
    MessagePack Hub Protocol 対応!
    https://github.com/nenoNaninu/TypedSignalR.Client.TypeScript
    属性を付与するだけ!
    OSS 紹介

    View Slide

  6. 5
    • SignalR 使ったアプリを快適に開発するための GUI を自動生成する library
    • 2 step で利用可能!
    • http pipeline に middleware の追加
    • Hub と Receiver を定義してる
    interface に属性を付与
    • JWT 認証 サポート
    • パラメータのユーザ定義型サポート
    • JSON で入力!
    SignalR 版 SwaggerUI
    TypedSignalR.Client.DevTools
    https://github.com/nenoNaninu/TypedSignalR.Client.DevTools
    OSS 紹介

    View Slide

  7. .NET 8 リリース直前
    6

    View Slide

  8. .NET 8 リリース直前
    7
    毎 年 恒 例
    https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-8/

    View Slide

  9. .NET 8 リリース直前
    8
    Σ(゚Д゚)
    https://github.com/dotnet/runtime/pull/86225
    ※ Tiered PGO = Dynamic PGO

    View Slide

  10. お品書き
    9
    • 歴史と概観
    • Instrumentation
    • 最適化例
    • 注意事項

    View Slide

  11. 歴史と概観
    10

    View Slide

  12. 歴史と概観
    11
    • .NET Core 2.1
    • Tiered compilation が導入。ただし既定では無効。
    • .NET Core 3.0
    • Tiered compilation が既定で有効
    • .NET 6
    • Dynamic PGO 導入。ただし既定では無効。
    • .NET 7
    • On-Stack Replacement (OSR) が導入
    • .NET 8
    • Dynamic PGO が 既定で有効
    歴史

    View Slide

  13. 歴史と概観
    12
    Tiered compilation とは?
    w/o Tiered compilation
    IL
    JIT
    https://github.com/dotnet/runtime/blob/v8.0.0-rc.2.23479.6/docs/design/features/tiered-compilation.md
    Optimized code

    View Slide

  14. 歴史と概観
    13
    Tiered compilation とは?
    w/o Tiered compilation
    IL
    JIT
    Optimized code
    x86
    assembly 等
    https://github.com/dotnet/runtime/blob/v8.0.0-rc.2.23479.6/docs/design/features/tiered-compilation.md

    View Slide

  15. 歴史と概観
    14
    Tiered compilation とは?
    w/o Tiered compilation w/ Tiered compilation
    IL
    JIT
    IL
    JIT
    Quick JIT
    Tier0
    Unoptimized code
    Tire1
    Optimized code
    Optimized code
    x86
    assembly 等
    https://github.com/dotnet/runtime/blob/v8.0.0-rc.2.23479.6/docs/design/features/tiered-compilation.md

    View Slide

  16. 歴史と概観
    15
    Tiered compilation とは?
    コンパイル速度優先
    最適化甘め
    w/o Tiered compilation w/ Tiered compilation
    IL
    JIT
    IL
    JIT
    Quick JIT
    Tier0
    Unoptimized code
    Tire1
    Optimized code
    Optimized code
    x86
    assembly 等
    https://github.com/dotnet/runtime/blob/v8.0.0-rc.2.23479.6/docs/design/features/tiered-compilation.md

    View Slide

  17. 歴史と概観
    16
    Tiered compilation とは?
    コンパイル速度優先
    最適化甘め
    w/o Tiered compilation w/ Tiered compilation
    IL
    JIT
    IL
    JIT
    Quick JIT
    Tier0
    Unoptimized code
    Tire1
    Optimized code
    .NET 8 では
    最低限度の最適化は
    されている
    Optimized code
    x86
    assembly 等
    https://github.com/dotnet/runtime/blob/v8.0.0-rc.2.23479.6/docs/design/features/tiered-compilation.md

    View Slide

  18. 歴史と概観
    17
    Tiered compilation とは?
    コンパイル速度優先
    最適化甘め
    w/o Tiered compilation w/ Tiered compilation
    IL
    JIT
    IL
    JIT
    Quick JIT
    Tier0
    Unoptimized code
    Tire1
    Optimized code
    高頻度で呼ばれる
    メソッドは最適化
    .NET 8 では
    最低限度の最適化は
    されている
    Optimized code
    x86
    assembly 等
    https://github.com/dotnet/runtime/blob/v8.0.0-rc.2.23479.6/docs/design/features/tiered-compilation.md

    View Slide

  19. 歴史と概観
    18
    • 実行中のメソッド (= On-Stack) の code をあるバージョンから
    別のバージョンに切り替える(Replacement)の技術。
    • e.g., Tire0 code から Tire1 code に切り替える等。
    • Backward branch (≒ループ) を含むメソッドで特に嬉しい。
    • .NET 6 以前はループを含んでいたら Tire1 直行だった。
    • OSRが実装されてない場合、無限ループが永遠に Tire0 で実行されてしまうため。
    • .NET 7 以後はループを含んでいても Tire0 で実行された後、
    ループ回数が多い場合にのみ Tire1 で実行可能になった。
    On-Stack Replacement (OSR) とは?
    https://github.com/dotnet/runtime/blob/v8.0.0-rc.2.23479.6/docs/design/features/OnStackReplacement.md
    Dynamic PGO 的に重要
    (無限ループをプロファイリング結果をもとに最適化可能)

    View Slide

  20. 歴史と概観
    19
    • PGO : Profile-guided optimization
    • その名の通り、プロファイリング結果をもとに最適化する。
    • Static PGO
    1. アプリケーションコードに計測用のコードを挿入し
    2. 主要なシナリオで実行 & 計測し
    3. その計測結果をもとに最適化をかけてリビルド
    • Dynamic PGO
    • Static PGO がやっていた事を全部実行時に行う
    • JIT の強み
    Dynamic PGO とは?
    https://github.com/dotnet/runtime/blob/v8.0.0-rc.2.23479.6/docs/design/features/DynamicPgo.md
    Tiering と OSR が導入されたおかげで
    ここまで出来るように

    View Slide

  21. 歴史と概観
    20
    • RedyToRun (R2R) を使うかどうかで違いが出てくる。
    Dynamic PGO の流れ
    https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-8/

    View Slide

  22. 歴史と概観
    21
    • 前提
    • Tier0 では可能な限り素早く JIT を終わらせたい。
    • 一方で最適化は時間がかかる。
    • 幾つかの最適化は上記を両立しながら出来る。
    • JIT が Tier0 で消費する時間の多くは runtime の VM とのやりとり
    • 型の解決など
    • 不要な分岐を大幅に削除できれば Tier 0 でのコンパイルを
    高速化しながら、code の品質も向上。
    Tier 0 で行われる最適化

    View Slide

  23. 歴史と概観
    22
    • 定数畳み込み
    • 定数式は (実行時ではなく) コンパイル時に評価
    • JIT intrinsic
    • If(typeof(T).IsValueType) の分岐が消失する等
    Tier 0 で行われる最適化

    View Slide

  24. 歴史と概観
    23
    • 定数畳み込み
    • 定数式は (実行時ではなく) コンパイル時に評価
    • JIT intrinsic
    • If(typeof(T).IsValueType) の分岐が消失する等
    Tier 0 で行われる最適化 定数畳み込み
    T が int じゃなかったら消し飛ぶ
    (.NET 7以前では Tier1 の最適化)

    View Slide

  25. 歴史と概観
    24
    だそうです。
    VM って結局なに。
    https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-8/

    View Slide

  26. Instrumentation
    25

    View Slide

  27. Instrumentation
    26
    • Dynamic PGO のためには各メソッドを何回呼んだかなど計測を
    する必要がある。
    • 計測する用のコードを実行時に挿し込む。
    • メソッドはマルチスレッドで同時に呼ばれる可能性がある。
    計測の必要性

    View Slide

  28. Instrumentation
    27
    • Dynamic PGO のためには各メソッドを何回呼んだかなど計測を
    する必要がある。
    • 計測する用のコードを実行時に挿し込む。
    • メソッドはマルチスレッドで同時に呼ばれる可能性がある。
    計測の必要性
    どうやってスレッドセーフに、正確に、効率的に計測するか?

    View Slide

  29. Instrumentation
    28
    1. 素朴に count++ (Racy)
    • .NET 7 以前はこうだった。
    • マルチスレッドだと一部のインクリメントが無効になるが、欲しいの
    は近似なので問題がないと思われていた。
    • 結構な誤差が出て間違った最適化がかかってしまう等の問題が出てき
    ていた。
    2. Interlocked.Increment
    • 正確なカウントは実現できた
    • ただしコストが非常に重たかった。
    3. Scalable Approximate Counting
    • 賢いカウント方法を導入。
    呼び出し回数の計測方法

    View Slide

  30. Scalable Approximate Counting
    29
    https://github.com/dotnet/runtime/blob/v8.0.0-rc.2.23479.6/docs/design/features/ScalableApproximateCounting.md

    View Slide

  31. Scalable Approximate Counting
    30
    log𝟐
    𝒄𝒐𝒖𝒏𝒕 (小数点以下切り捨て)
    https://github.com/dotnet/runtime/blob/v8.0.0-rc.2.23479.6/docs/design/features/ScalableApproximateCounting.md

    View Slide

  32. Scalable Approximate Counting
    31
    log𝟐
    𝒄𝒐𝒖𝒏𝒕 (小数点以下切り捨て)
    log𝟐
    𝒄𝒐𝒖𝒏𝒕 ≥ 𝟏𝟑
    𝒄𝒐𝒖𝒏𝒕 ≥ 𝟐𝟏𝟑 = 𝟖𝟏𝟗𝟐
    https://github.com/dotnet/runtime/blob/v8.0.0-rc.2.23479.6/docs/design/features/ScalableApproximateCounting.md

    View Slide

  33. Scalable Approximate Counting
    32
    log𝟐
    𝒄𝒐𝒖𝒏𝒕 (小数点以下切り捨て)
    log𝟐
    𝒄𝒐𝒖𝒏𝒕 ≥ 𝟏𝟑
    𝒄𝒐𝒖𝒏𝒕 ≥ 𝟐𝟏𝟑 = 𝟖𝟏𝟗𝟐
    𝒅𝒆𝒍𝒕𝒂 ቐ
    𝟎𝒃𝟏𝟎 (log𝟐
    𝒄𝒐𝒖𝒏𝒕 = 𝟏𝟑)
    𝟎𝒃𝟏𝟎𝟎 (log𝟐
    𝒄𝒐𝒖𝒏𝒕 = 𝟏𝟒)

    2進数リテラル
    https://github.com/dotnet/runtime/blob/v8.0.0-rc.2.23479.6/docs/design/features/ScalableApproximateCounting.md

    View Slide

  34. Scalable Approximate Counting
    33
    log𝟐
    𝒄𝒐𝒖𝒏𝒕 (小数点以下切り捨て)
    log𝟐
    𝒄𝒐𝒖𝒏𝒕 ≥ 𝟏𝟑
    𝒄𝒐𝒖𝒏𝒕 ≥ 𝟐𝟏𝟑 = 𝟖𝟏𝟗𝟐
    𝒅𝒆𝒍𝒕𝒂 ቐ
    𝟎𝒃𝟏𝟎 (log𝟐
    𝒄𝒐𝒖𝒏𝒕 = 𝟏𝟑)
    𝟎𝒃𝟏𝟎𝟎 (log𝟐
    𝒄𝒐𝒖𝒏𝒕 = 𝟏𝟒)

    2進数リテラル
    𝒅𝒆𝒍𝒕𝒂 − 𝟏 ቐ
    𝟎𝒃𝟏 (log𝟐
    𝒄𝒐𝒖𝒏𝒕 = 𝟏𝟑)
    𝟎𝒃𝟏𝟏 (log𝟐
    𝒄𝒐𝒖𝒏𝒕 = 𝟏𝟒)

    https://github.com/dotnet/runtime/blob/v8.0.0-rc.2.23479.6/docs/design/features/ScalableApproximateCounting.md

    View Slide

  35. Scalable Approximate Counting
    34
    log𝟐
    𝒄𝒐𝒖𝒏𝒕 (小数点以下切り捨て)
    log𝟐
    𝒄𝒐𝒖𝒏𝒕 ≥ 𝟏𝟑
    𝒄𝒐𝒖𝒏𝒕 ≥ 𝟐𝟏𝟑 = 𝟖𝟏𝟗𝟐
    𝒅𝒆𝒍𝒕𝒂 ቐ
    𝟎𝒃𝟏𝟎 (log𝟐
    𝒄𝒐𝒖𝒏𝒕 = 𝟏𝟑)
    𝟎𝒃𝟏𝟎𝟎 (log𝟐
    𝒄𝒐𝒖𝒏𝒕 = 𝟏𝟒)

    2進数リテラル
    𝒅𝒆𝒍𝒕𝒂 − 𝟏 ቐ
    𝟎𝒃𝟏 (log𝟐
    𝒄𝒐𝒖𝒏𝒕 = 𝟏𝟑)
    𝟎𝒃𝟏𝟏 (log𝟐
    𝒄𝒐𝒖𝒏𝒕 = 𝟏𝟒)

    いい感じに mask になる
    https://github.com/dotnet/runtime/blob/v8.0.0-rc.2.23479.6/docs/design/features/ScalableApproximateCounting.md

    View Slide

  36. Scalable Approximate Counting
    35
    log𝟐
    𝒄𝒐𝒖𝒏𝒕 (小数点以下切り捨て)
    log𝟐
    𝒄𝒐𝒖𝒏𝒕 ≥ 𝟏𝟑
    𝒄𝒐𝒖𝒏𝒕 ≥ 𝟐𝟏𝟑 = 𝟖𝟏𝟗𝟐
    𝒅𝒆𝒍𝒕𝒂 ቐ
    𝟎𝒃𝟏𝟎 (log𝟐
    𝒄𝒐𝒖𝒏𝒕 = 𝟏𝟑)
    𝟎𝒃𝟏𝟎𝟎 (log𝟐
    𝒄𝒐𝒖𝒏𝒕 = 𝟏𝟒)

    32bit の rand と delta-1 の
    論理積を取ると…
    2進数リテラル
    𝒅𝒆𝒍𝒕𝒂 − 𝟏 ቐ
    𝟎𝒃𝟏 (log𝟐
    𝒄𝒐𝒖𝒏𝒕 = 𝟏𝟑)
    𝟎𝒃𝟏𝟏 (log𝟐
    𝒄𝒐𝒖𝒏𝒕 = 𝟏𝟒)

    いい感じに mask になる
    https://github.com/dotnet/runtime/blob/v8.0.0-rc.2.23479.6/docs/design/features/ScalableApproximateCounting.md

    View Slide

  37. Scalable Approximate Counting
    36
    log𝟐
    𝒄𝒐𝒖𝒏𝒕 (小数点以下切り捨て)
    log𝟐
    𝒄𝒐𝒖𝒏𝒕 ≥ 𝟏𝟑
    𝒄𝒐𝒖𝒏𝒕 ≥ 𝟐𝟏𝟑 = 𝟖𝟏𝟗𝟐
    𝒅𝒆𝒍𝒕𝒂 ቐ
    𝟎𝒃𝟏𝟎 (log𝟐
    𝒄𝒐𝒖𝒏𝒕 = 𝟏𝟑)
    𝟎𝒃𝟏𝟎𝟎 (log𝟐
    𝒄𝒐𝒖𝒏𝒕 = 𝟏𝟒)

    𝒅𝒆𝒍𝒕𝒂 − 𝟏 ቐ
    𝟎𝒃𝟏 (log𝟐
    𝒄𝒐𝒖𝒏𝒕 = 𝟏𝟑)
    𝟎𝒃𝟏𝟏 (log𝟐
    𝒄𝒐𝒖𝒏𝒕 = 𝟏𝟒)

    32bit の rand と delta-1 の
    論理積を取ると…
    初回 ½ で true (1bit の mask)
    この時 delta = 0b10 = 2
    いい感じに mask になる
    2進数リテラル
    https://github.com/dotnet/runtime/blob/v8.0.0-rc.2.23479.6/docs/design/features/ScalableApproximateCounting.md

    View Slide

  38. Scalable Approximate Counting
    37
    log𝟐
    𝒄𝒐𝒖𝒏𝒕 (小数点以下切り捨て)
    log𝟐
    𝒄𝒐𝒖𝒏𝒕 ≥ 𝟏𝟑
    𝒄𝒐𝒖𝒏𝒕 ≥ 𝟐𝟏𝟑 = 𝟖𝟏𝟗𝟐
    𝒅𝒆𝒍𝒕𝒂 ቐ
    𝟎𝒃𝟏𝟎 (log𝟐
    𝒄𝒐𝒖𝒏𝒕 = 𝟏𝟑)
    𝟎𝒃𝟏𝟎𝟎 (log𝟐
    𝒄𝒐𝒖𝒏𝒕 = 𝟏𝟒)

    𝒅𝒆𝒍𝒕𝒂 − 𝟏 ቐ
    𝟎𝒃𝟏 (log𝟐
    𝒄𝒐𝒖𝒏𝒕 = 𝟏𝟑)
    𝟎𝒃𝟏𝟏 (log𝟐
    𝒄𝒐𝒖𝒏𝒕 = 𝟏𝟒)

    32bit の rand と delta-1 の
    論理積を取ると…
    初回 ½ で true (1bit の mask)
    この時 delta = 0b10 = 2
    いい感じに mask になる
    2進数リテラル
    次に ¼ で true (2bit の mask)
    この時 delta = 0b100 = 4
    https://github.com/dotnet/runtime/blob/v8.0.0-rc.2.23479.6/docs/design/features/ScalableApproximateCounting.md

    View Slide

  39. Scalable Approximate Counting
    38
    log𝟐
    𝒄𝒐𝒖𝒏𝒕 (小数点以下切り捨て)
    log𝟐
    𝒄𝒐𝒖𝒏𝒕 ≥ 𝟏𝟑
    𝒄𝒐𝒖𝒏𝒕 ≥ 𝟐𝟏𝟑 = 𝟖𝟏𝟗𝟐
    𝒅𝒆𝒍𝒕𝒂 ቐ
    𝟎𝒃𝟏𝟎 (log𝟐
    𝒄𝒐𝒖𝒏𝒕 = 𝟏𝟑)
    𝟎𝒃𝟏𝟎𝟎 (log𝟐
    𝒄𝒐𝒖𝒏𝒕 = 𝟏𝟒)

    𝒅𝒆𝒍𝒕𝒂 − 𝟏 ቐ
    𝟎𝒃𝟏 (log𝟐
    𝒄𝒐𝒖𝒏𝒕 = 𝟏𝟑)
    𝟎𝒃𝟏𝟏 (log𝟐
    𝒄𝒐𝒖𝒏𝒕 = 𝟏𝟒)

    32bit の rand と delta-1 の
    論理積を取ると…
    初回 ½ で true (1bit の mask)
    この時 delta = 0b10 = 2
    いい感じに mask になる
    2進数リテラル
    次に ¼ で true (2bit の mask)
    この時 delta = 0b100 = 4
    最終的に delta が加算
    https://github.com/dotnet/runtime/blob/v8.0.0-rc.2.23479.6/docs/design/features/ScalableApproximateCounting.md

    View Slide

  40. Scalable Approximate Counting
    39
    • X 軸が最終的な試行回数、Y 軸が精度
    • CPU のスレッド数は 12
    精度は?
    https://github.com/dotnet/runtime/blob/v8.0.0-rc.2.23479.6/docs/design/features/ScalableApproximateCounting.md

    View Slide

  41. Scalable Approximate Counting
    40
    • X 軸が最終的な試行回数、Y 軸が精度
    • CPU のスレッド数は 12
    精度は?
    Scalable が優秀
    https://github.com/dotnet/runtime/blob/v8.0.0-rc.2.23479.6/docs/design/features/ScalableApproximateCounting.md

    View Slide

  42. Scalable Approximate Counting
    41
    • X 軸が最終的な試行回数、Y 軸が精度
    • CPU のスレッド数は 12
    精度は?
    Scalable が優秀
    一方で Racy は
    許容できない誤差
    https://github.com/dotnet/runtime/blob/v8.0.0-rc.2.23479.6/docs/design/features/ScalableApproximateCounting.md

    View Slide

  43. Scalable Approximate Counting
    42
    • Racy を取り除いた図。
    • Scalable はワーストケースでも 3% のズレで済んでいる。
    精度は?
    https://github.com/dotnet/runtime/blob/v8.0.0-rc.2.23479.6/docs/design/features/ScalableApproximateCounting.md

    View Slide

  44. Scalable Approximate Counting
    43
    • X 軸が最終的な試行回数、Y 軸がかかった合計時間
    • 217 を超えたあたりから Interlocked が顕著に高コストに。
    速度は?
    https://github.com/dotnet/runtime/blob/v8.0.0-rc.2.23479.6/docs/design/features/ScalableApproximateCounting.md

    View Slide

  45. Scalable Approximate Counting
    44
    • X 軸が最終的な試行回数、Y 軸がかかった合計時間
    • 217 を超えたあたりから Interlocked が顕著に高コストに。
    速度は?
    Scalable と Racy は
    ほぼ変らない性能
    https://github.com/dotnet/runtime/blob/v8.0.0-rc.2.23479.6/docs/design/features/ScalableApproximateCounting.md

    View Slide

  46. Instrumentation
    45
    • Dynamic PGO では devirtualization 等のために
    virtual method / interface method の呼び出し先の具象型が何で、
    それが何回実行されたか…とかが知りたい。
    • 最もよく呼ばれる型は devirtualization する。
    • しかし、呼び出された具象型を正確にカウントすることは高価。
    • なお、PGO 的に知りたいのは割合。正確な回数でない。
    呼び出し回数以外の counting

    View Slide

  47. Instrumentation
    46
    • Dynamic PGO では devirtualization 等のために
    virtual method / interface method の呼び出し先の具象型が何で、
    それが何回実行されたか…とかが知りたい。
    • 最もよく呼ばれる型は devirtualization する。
    • しかし、呼び出された具象型を正確にカウントすることは高価。
    • なお、PGO 的に知りたいのは割合。正確な回数でない。
    呼び出し回数以外の counting
    さてどうするか 🤔

    View Slide

  48. Instrumentation
    47
    • Sampling とは
    • n 個ある母集団から k 個取り出す操作
    • n が既知かつそこそこのサイズまでは簡単
    • Dynamic PGO で求められる sampling はちょっと難しい
    • 未知の n に対応
    • 母集団が呼び出しの集合なので、当然 n は未知であり、増加し続ける
    • 省メモリかつ高速
    • 全ての呼び出しを保存とかは当然不適切。
    • つまり、過去の呼び出しを遡るとかもできない。
    • 一様なサンプリング
    Sampling して解決。ただし…

    View Slide

  49. Instrumentation
    48
    • Sampling とは
    • n 個ある母集団から k 個取り出す操作
    • n が既知かつそこそこのサイズまでは簡単
    • Dynamic PGO で求められる sampling はちょっと難しい
    • 未知の n に対応
    • 母集団が呼び出しの集合なので、当然 n は未知であり、増加し続ける
    • 省メモリかつ高速
    • 全ての呼び出しを保存とかは当然不適切。
    • つまり、過去の呼び出しを遡るとかもできない。
    • 一様なサンプリング
    Sampling して解決。ただし…
    要するに終端の分からないストリームから
    一様にサンプリングしないといけない

    View Slide

  50. Instrumentation
    49
    • Reservoir : 貯水池
    • サンプリング結果が reservoir に保存される
    • ストリームを1方向に舐めながら、
    一様にサンプリング可能な素敵アルゴリズム
    • これが一様サンプリングである証明は
    いろいろ文献あるのでそちらを参照のこと
    • なお、こまやかな調整が必要だった模様。
    • https://github.com/dotnet/runtime/pull/87332
    Reservoir sampling

    View Slide

  51. Instrumentation
    50
    • Reservoir : 貯水池
    • サンプリング結果が reservoir に保存される
    • ストリームを1方向に舐めながら、
    一様にサンプリング可能な素敵アルゴリズム
    • これが一様サンプリングである証明は
    いろいろ文献あるのでそちらを参照のこと
    • なお、こまやかな調整が必要だった模様。
    • https://github.com/dotnet/runtime/pull/87332
    Reservoir sampling

    View Slide

  52. Instrumentation
    51
    • 静的な解析に基づく最適化、いろいろありますよね。
    • 分岐予測とか。
    • Dynamic PGO は静的な profile と instrumentation によって得られた
    profile を合成して最適化を行う。
    • profile synthesis とかいう。
    Dynamic PGO による最適化は
    instrumentation にのみ基づくわけではない
    https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-8/

    View Slide

  53. 最適化例
    52

    View Slide

  54. 最適化例
    53
    • Devirtualization
    • Inlining
    で、実際どういう最適化されるの?
    他にもいろいろあるけど
    大きな柱はこの 2 つ

    View Slide

  55. 最適化例
    54
    Devirtualization + Inlining の具体例

    View Slide

  56. 最適化例
    55
    Devirtualization + Inlining の具体例

    View Slide

  57. 最適化例
    56
    どんな最適化が?

    View Slide

  58. 最適化例
    57
    どんな最適化が?

    View Slide

  59. 最適化例
    58
    どんな最適化が?
    PGO が無ければ普通に interface の dispatch.
    Dynamic PGO により devirtualization

    View Slide

  60. 最適化例
    59
    どんな最適化が?
    PGO が無ければ普通に interface の dispatch.
    Dynamic PGO により devirtualization
    Guarded
    devirtualization
    (GDV)
    と呼ばれる最適化

    View Slide

  61. 最適化例
    60
    どんな最適化が?
    PGO が無ければ普通に interface の dispatch.
    Dynamic PGO により devirtualization
    Guarded
    devirtualization
    (GDV)
    と呼ばれる最適化

    View Slide

  62. 最適化例
    61
    どんな最適化が?
    PGO が無ければ普通に interface の dispatch.
    Dynamic PGO により devirtualization
    さらに Inline 化
    Guarded
    devirtualization
    (GDV)
    と呼ばれる最適化

    View Slide

  63. 最適化例
    62
    どんな最適化が?
    PGO が無ければ普通に interface の dispatch.
    Dynamic PGO により devirtualization
    さらに Inline 化
    Guarded
    devirtualization
    (GDV)
    と呼ばれる最適化

    View Slide

  64. 最適化例
    63
    x86 assembly を覗いてみると…
    .NET 7 .NET 8

    View Slide

  65. 最適化例
    64
    x86 assembly を覗いてみると…
    .NET 7 .NET 8
    型のチェックが
    挟まっている

    View Slide

  66. 最適化例
    65
    x86 assembly を覗いてみると…
    .NET 7 .NET 8
    型のチェックが
    挟まっている
    42 が
    埋め込ま
    れている
    (0x2A は 42)

    View Slide

  67. 最適化例
    66
    • 既定では 1 つ。
    • DOTNET_JitGuardedDevirtualizationMaxTypeChecks 環境変数で設定可能。
    Guarded devirtualization される型は幾つまで?

    View Slide

  68. 最適化例
    67
    • 既定では 1 つ。
    • DOTNET_JitGuardedDevirtualizationMaxTypeChecks 環境変数で設定可能。
    Guarded devirtualization される型は幾つまで?
    必ずしもパフォーマンスが改善するとは
    限らないので注意(悪化する場合も)

    View Slide

  69. 最適化例
    68
    • 既定では 1 つ。
    • DOTNET_JitGuardedDevirtualizationMaxTypeChecks 環境変数で設定可能。
    Guarded devirtualization される型は幾つまで?
    Generic host 全盛の昨今では
    1 interface 1 class で DI に登録されるので
    既定で十分高速化が期待できる
    必ずしもパフォーマンスが改善するとは
    限らないので注意(悪化する場合も)

    View Slide

  70. 最適化例
    69
    Delegate にも Guarded devirtualization + inlining

    View Slide

  71. 最適化例
    70
    Delegate にも Guarded devirtualization + inlining
    常に _func が
    呼び出されているだけ

    View Slide

  72. 最適化例
    71
    Delegate にも Guarded devirtualization + inlining
    常に _func が
    呼び出されているだけ
    毎回 delegate の invoke は
    無駄が大きい

    View Slide

  73. 最適化例
    72
    • GDV + inlining により既知の delegate なら
    delegate の invoke を避けるようなコードを生成
    Delegate にも Guarded devirtualization + inlining
    疑似コード

    View Slide

  74. 最適化例
    73
    • GDV + inlining により既知の delegate なら
    delegate の invoke を避けるようなコードを生成
    Delegate にも Guarded devirtualization + inlining
    疑似コード
    Delegate の invoke は
    避けられた

    View Slide

  75. 最適化例
    74
    • GDV + inlining により既知の delegate なら
    delegate の invoke を避けるようなコードを生成
    Delegate にも Guarded devirtualization + inlining
    疑似コード
    Delegate の invoke は
    避けられた
    ループの中で
    何度も比較は無駄…

    View Slide

  76. 最適化例
    75
    • Hoisting という最適化を行う
    • ループの中で不変なのであれば、ループの外に出す
    Delegate にも Guarded devirtualization + inlining + α
    疑似コード
    最適化対象の delegate かの
    比較はループの外に出せた

    View Slide

  77. 最適化例
    76
    • Hoisting という最適化を行う
    • ループの中で不変なのであれば、ループの外に出す
    Delegate にも Guarded devirtualization + inlining + α
    疑似コード
    最適化対象の delegate かの
    比較はループの外に出せた
    ループの中で分岐がまだある…

    View Slide

  78. 最適化例
    77
    • Loop cloning という最適化を行う
    Delegate にも Guarded devirtualization + inlining + α
    疑似コード

    View Slide

  79. 最適化例
    78
    • Loop cloning という最適化を行う
    Delegate にも Guarded devirtualization + inlining + α
    疑似コード
    最終的に大幅な
    高速化

    View Slide

  80. 注意事項
    79

    View Slide

  81. 注意事項
    80
    • マイクロベンチマークなどのいくつかのパターンでは、
    Dynamic PGO を強く意識する必要がある。
    • Dynamic PGO on/off して双方でベンチマークとるなどの対策が必要。
    • なぜ?
    • マイクロベンチマークで Dynamic PGO が有効になってしまうと、
    開発者が書いたコードが原因で高速化が実現できたのか、
    Dynamic PGO によって高速化が実現できたのか、分からなくなってしまう。
    高速化の原因は Dynamic PGO かも?

    View Slide

  82. 注意事項
    81
    • 開発者が書く際に手動で最適化をしなくて良くなる訳ではない。
    • 例えば具象型でいいフィールドを interface にしない、とか。
    手動の最適化はいまだ有効
    .NET 8 からこれを促す
    analyzer が入るそうです

    View Slide

  83. まとめ
    82
    • 歴史と概観
    • Tiered compilation
    • On-Stack Replacement
    • Dynamic PGO
    • Tier 0 での最適化
    • Instrumentation
    • Scalable Approximate Counting
    • Reservoir sampling
    • 最適化例
    • Guarded Devirtualization
    • Inlining
    • 注意事項
    • ベンチマークとか取る際には気を付けよう
    • 手動での最適化は未だ大事

    View Slide