Slide 1

Slide 1 text

.NET Conf 2024 / dotnet lab 2024/12/21 何縫ねの。

Slide 2

Slide 2 text

何縫ねの。 NTT コミュニケーションズ イノベーションセンター Microsoft MVP for Developer Technologies (2024~) .NET / Web Development nenoNaninu @nenoMake ブログ https://blog.neno.dev その他 https://neno.dev

Slide 3

Slide 3 text

OSS SignalR 周りのいろいろ開発しています •TypedSignalR.Client • C# の SignalR Client を強く型付けするための Source Generator •TypedSignalR.Client.TypeScript • TypeScript の SignalR Client を強く型付けするための .NET Tool / library •TypedSignalR.Client.DevTools • SignalR 向けに Swagger UI 相当の GUI を自動生成する library •AspNetCore.SignalR.OpenTelemetry • SignalR の OpenTelemetry 対応 + log 強化 https://github.com/nenoNaninu

Slide 4

Slide 4 text

Agenda Dynamic PGO Tier 0 Optimization Object Stack Allocation GC VM Threading Reflection SearchValues Span

Slide 5

Slide 5 text

Dynamic PGO

Slide 6

Slide 6 text

Dynamic PGO 過去に Dynamic PGO について詳細に語っています https://speakerdeck.com/nenonaninu/dot-net-8-deji-ding-deyou-xiao-ninatuta-dynamic-pgo-nituite

Slide 7

Slide 7 text

Dynamic PGO 過去に Dynamic PGO について詳細に語っています https://speakerdeck.com/nenonaninu/dot-net-8-deji-ding-deyou-xiao-ninatuta-dynamic-pgo-nituite .NET の Dynamic PGO について 日本語で解説している 最も詳しいスライド

Slide 8

Slide 8 text

Dynamic PGO •.NET 8 まで • Devirtualization • virtual method, interface method, delegate の呼び出し高速化 • Inlining •.NET 9 から • Cast の高速化 • (T)obj • obj is T

Slide 9

Slide 9 text

Dynamic PGO 事前知識

Slide 10

Slide 10 text

Dynamic PGO 事前知識 オブジェクトの型情報は Header の後ろに存在

Slide 11

Slide 11 text

Dynamic PGO Cast に関する最適化

Slide 12

Slide 12 text

Dynamic PGO .NET 8

Slide 13

Slide 13 text

Dynamic PGO .NET 8 必ず IsInstanceOfClass を呼び出して obj が B かを判定

Slide 14

Slide 14 text

Dynamic PGO .NET 8 必ず IsInstanceOfClass を呼び出して obj が B かを判定

Slide 15

Slide 15 text

Dynamic PGO .NET 8 必ず IsInstanceOfClass を呼び出して obj が B かを判定 B の TypeHandle と _obj が渡される

Slide 16

Slide 16 text

Dynamic PGO .NET 8 必ず IsInstanceOfClass を呼び出して obj が B かを判定 B の TypeHandle と _obj が渡される 継承等の関係上単純な比較にはならない

Slide 17

Slide 17 text

Dynamic PGO IsInstanceOfClass の実装詳細 或いはそもそも TypeHandle とは何か https://learn.microsoft.com/en-us/archive/msdn-magazine/2005/may/net-framework-internals-how-the-clr-creates-runtime-objects • Type 毎に MethodTalbe が1つ存在する • TypeHandle は MethodTalbe へのポインタ • MethodTable には Type に関する様々な情報 • 型の種別(class, struct, interface) • 実装している interface の数 • 等々

Slide 18

Slide 18 text

Dynamic PGO .NET 9

Slide 19

Slide 19 text

Dynamic PGO .NET 9 重要なのはここ。 JIT は C が B から継承されている事を知っている。 なので obj が C かを直接判定(cmp)し高速化

Slide 20

Slide 20 text

Dynamic PGO .NET 9 C であれば下にそのまま処理が進み C ではなかったら M00_L01 に飛ぶ 重要なのはここ。 JIT は C が B から継承されている事を知っている。 なので obj が C かを直接判定(cmp)し高速化

Slide 21

Slide 21 text

Tier 0 Optimization 流石にこんなコード書く人はいないはずですが… Analyzer にも怒られますしね!

Slide 22

Slide 22 text

Tier 0 Optimization でも generics なら? まぁ、あるよね。

Slide 23

Slide 23 text

Tier 0 Optimization でも generics なら? まぁ、あるよね。 .NET 8 までは、Tire 0 では最適化されず boxing が発生 Tire 1 で最適化され、呼び出し自体が消し飛ぶ

Slide 24

Slide 24 text

Tier 0 Optimization でも generics なら? まぁ、あるよね。 .NET 8 までは、Tire 0 では最適化されず boxing が発生 Tire 1 で最適化され、呼び出し自体が消し飛ぶ .NET 9 からは Tire 0 で呼び出しが消し飛ぶ

Slide 25

Slide 25 text

Object Stack Allocation heap に allocation が発生する所を stack に allocation する最適化

Slide 26

Slide 26 text

Object Stack Allocation heap に allocation が発生する所を stack に allocation する最適化 普通に考えれば new MyObj() で heap に allocation が走る

Slide 27

Slide 27 text

Object Stack Allocation .NET 8

Slide 28

Slide 28 text

Object Stack Allocation .NET 8 CORINFO_HELP_NEWSFAST で allocate

Slide 29

Slide 29 text

Object Stack Allocation .NET 8 CORINFO_HELP_NEWSFAST で allocate new small object fast の意(おそらく)

Slide 30

Slide 30 text

Object Stack Allocation .NET 9

Slide 31

Slide 31 text

Object Stack Allocation .NET 9 Heap に対する allocation なし

Slide 32

Slide 32 text

Object Stack Allocation .NET 9 Object stack allocation + inlining により、定数を返すだけになる Heap に対する allocation なし

Slide 33

Slide 33 text

Object Stack Allocation .NET 9 Object stack allocation + inlining により、定数を返すだけになる Heap に対する allocation なし 流石にパフォーマンス アピール用のコード 過ぎないか?

Slide 34

Slide 34 text

Object Stack Allocation .NET 9 Object stack allocation + inlining により、定数を返すだけになる Heap に対する allocation なし 流石にパフォーマンス アピール用のコード 過ぎないか? 実際のユースケースでは どうだろうか?

Slide 35

Slide 35 text

Object Stack Allocation そもそも object stack allocation の対象となるパターンは? オブジェクト参照が現在のスタックフレームから離れないことを 簡単に証明できるケースのみ最適化

Slide 36

Slide 36 text

Object Stack Allocation そもそも object stack allocation の対象となるパターンは? オブジェクト参照が現在のスタックフレームから離れないことを 簡単に証明できるケースのみ最適化 メソッド呼び出し間を分析 (interprocedural analysis) して 最適化などは行わない

Slide 37

Slide 37 text

こういうユースケースで嬉しい Object Stack Allocation

Slide 38

Slide 38 text

.NET 8 こういうユースケースで嬉しい Object Stack Allocation

Slide 39

Slide 39 text

.NET 8 こういうユースケースで嬉しい Object Stack Allocation MyStruct.Dispose() の実装が 空なので inlining され ここまで圧縮される

Slide 40

Slide 40 text

.NET 8 こういうユースケースで嬉しい Object Stack Allocation MyStruct.Dispose() の実装が 空なので inlining され ここまで圧縮される using で dispose する場合は この展開のされ方になるが boxing は発生しない

Slide 41

Slide 41 text

.NET 8 こういうユースケースで嬉しい Object Stack Allocation MyStruct.Dispose() の実装が 空なので inlining され ここまで圧縮される using で dispose する場合は この展開のされ方になるが boxing は発生しない .NET 9

Slide 42

Slide 42 text

.NET 8 こういうユースケースで嬉しい .NET 9 Object Stack Allocation disposable はこのフレーム内で 参照が握られたりしない事が証明できるため、 object stack allocation による最適化の対象となる using で dispose する場合は この展開のされ方になるが boxing は発生しない MyStruct.Dispose() の実装が 空なので inlining され ここまで圧縮される

Slide 43

Slide 43 text

.NET 8 こういうユースケースで嬉しい .NET 9 Object Stack Allocation disposable はこのフレーム内で 参照が握られたりしない事が証明できるため、 object stack allocation による最適化の対象となる using で dispose する場合は この展開のされ方になるが boxing は発生しない MyStruct.Dispose() の実装が 空なので inlining され ここまで圧縮される

Slide 44

Slide 44 text

GC • メモリの消費量を抑える • 一回あたりの GC の時間を抑える • タイムアウト有り • GUI をフリーズさせないため • CPU のコア数が 1 つなら必ず Workstation GC • ヒープは 1 つ Server GC • スループットの最大化 • メモリの消費量は大きめ • 一回あたりの GC の時間長め • タイムアウト無し • ASP.NET Core の既定 • ただし CPU コアが2つ以上の場合 • CPUコア数=ヒープ数 • ただし既定の場合 Workstation GC GC の種別

Slide 45

Slide 45 text

GC AdaptationMode • スループットは大事だけど、メモリの消費量も抑えたい • DATAS (Dynamically Adapting To Application Sizes) の導入 • .NET 8 で導入された、しかし既定で有効にはなっていなかった • .NET 9 から既定で有効に • DATAS のお仕事は「アプリケーションのサイズに適応する」事 • ヒープ数の調整 • スループットと消費するメモリと GC の頻度に応じて調整 • 完全な compaction を走らせるための allocation budget の調整 https://maoni0.medium.com/dynamically-adapting-to-application-sizes-2d72fcb6f1ea

Slide 46

Slide 46 text

GC AdaptationMode • スループットは大事だけど、メモリの消費量も抑えたい • DATAS (Dynamically Adapting To Application Sizes) の導入 • .NET 8 で導入された、しかし既定で有効にはなっていなかった • .NET 9 から既定で有効に • DATAS のお仕事は「アプリケーションのサイズに適応する」事 • ヒープ数の調整 • スループットと消費するメモリと GC の頻度に応じて調整 • 完全な compaction を走らせるための allocation budget の調整 https://maoni0.medium.com/dynamically-adapting-to-application-sizes-2d72fcb6f1ea 完全な compaction を走らせないと 何時まで経っても余計なメモリを食いっぱなし

Slide 47

Slide 47 text

GC AdaptationMode • スループットは大事だけど、メモリの消費量も抑えたい • DATAS (Dynamically Adapting To Application Sizes) の導入 • .NET 8 で導入された、しかし既定で有効にはなっていなかった • .NET 9 から既定で有効に • DATAS のお仕事は「アプリケーションのサイズに適応する」事 • ヒープ数の調整 • スループットと消費するメモリと GC の頻度に応じて調整 • 完全な compaction を走らせるための allocation budget の調整 https://maoni0.medium.com/dynamically-adapting-to-application-sizes-2d72fcb6f1ea メモリの消費量気にせず 最大のスループットを求めるなら 今のところ AdaptationMode は切った方が良い 完全な compaction を走らせないと 何時まで経っても余計なメモリを食いっぱなし

Slide 48

Slide 48 text

VM FCALL から QCALL へ • managed code から runtime code の呼び出し手段 • QCALL • 実質 runtime で定義された関数への P/Invoke • GC を止めない • FCALL • より特殊で複雑なメカニズムを持つ呼び出し手段 • QCALL に比較すると重い • GC を止める • 以前は FCALL が主流だったが、リリース毎に徐々に QCALL に置き換えられている

Slide 49

Slide 49 text

VM 例外の高速化 • Native AOT の例外ハンドリングの実装を coreclr に移植 • 例外処理が 3.5 ~ 4 倍高速化(自称) • 実際には 2~4倍くらい? • global spinlock の排除

Slide 50

Slide 50 text

Threading System.Threading.Lock • .NET 8 までは object で lock するのが常套手段 • .NET 9 からは System.Threading.Lock の利用を推奨

Slide 51

Slide 51 text

Threading C# 13 が Lock を特別扱い

Slide 52

Slide 52 text

Threading System.Threading.Interlocked • Exchange / CompareExchange に overload が追加 • byte • sbyte • ushort • short

Slide 53

Slide 53 text

Threading System.Threading.Interlocked • Exchange / CompareExchange に overload が追加 • byte • sbyte • ushort • short Parallel.ForAsync (.NET 8~) 内部でも 利用され、パフォーマンス改善

Slide 54

Slide 54 text

Threading System.Threading.Interlocked • Exchange / CompareExchange に overload が追加 • byte • sbyte • ushort • short • Exchange / CompareExchange の class 制約の排除 • generics で primitive type が使えるようになったため、使い勝手向上 • enum に対応 Parallel.ForAsync (.NET 8~) 内部でも 利用され、パフォーマンス改善

Slide 55

Slide 55 text

Threading System.Threading.Interlocked • Exchange / CompareExchange に overload が追加 • byte • sbyte • ushort • short • Exchange / CompareExchange の class 制約の排除 • generics で primitive type が使えるようになったため、使い勝手向上 • enum に対応 Parallel.ForAsync (.NET 8~) 内部でも 利用され、パフォーマンス改善 CompareExchange の都合上 int (4 byte) にしていたところを .NET 9 からは bool (1 byte) に置き換えられるため、オブジェクトのサイズを落とせる

Slide 56

Slide 56 text

Threading Task.WhenEach • 完了した task を順次返してくれる新しい API WhenEach が無い時代は こんな事をする必要があった. O(N^2) .NET 9

Slide 57

Slide 57 text

Reflection ConstructorInvoker (.NET 8) • ConstructorInvoker 自体は .NET 8 で導入された型 • .NET 9 から Ms.Ex.DependencyInjection 内部で利用されるようになった 同種の MethodInvoker も .NET 8 で導入

Slide 58

Slide 58 text

SearchValues .NET 9 から SearchValues が string で使えるように…! • .NET 8 で導入された SearchValues • .NET 8 時点では byte と char のみの対応だった

Slide 59

Slide 59 text

SearchValues SearchValues を使わない書き方とそのパフォーマンス

Slide 60

Slide 60 text

SearchValues SearchValues を使わない書き方とそのパフォーマンス

Slide 61

Slide 61 text

SearchValues SearchValues を使わない書き方とそのパフォーマンス

Slide 62

Slide 62 text

SearchValues SearchValues を使わない書き方とそのパフォーマンス

Slide 63

Slide 63 text

SearchValues SearchValues を使わない書き方とそのパフォーマンス Regex の中でも使われるので .NET 9 で再コンパイルすれば高速になる可能性あり!

Slide 64

Slide 64 text

Span ReadOnlySpan を引数に持つメソッドへの params アノテーション祭り • C# 13 の新機能である params collection にあらゆるところで対応 • 再コンパイルするだけで高速化の可能性…! • params T[] が呼ばれていた箇所が params ReadOnlySpan が利用されるようになるため • 実際に params が追加された API 群 (一部) • StringBuilder • Path / StreamWriter / TextWriter • Task / CancellationTokenSource • ImmutableArray / ImmutableHashSet / ImmutableList / ImmutableQueue / ImmutableStack • Counter / UpDownCounter / Histogram / Measurement / TagList

Slide 65

Slide 65 text

Span ReadOnlySpan の初期化の改善 • .NET 8 時点で byte / sbyte / bool の定数の ReadOnlySpan を作成する場合は高速 • アセンブリのデータを ReadOnlySpan で読むだけなので非常に高速

Slide 66

Slide 66 text

Span ReadOnlySpan の初期化の改善 • .NET 8 時点で byte / sbyte / bool の定数の ReadOnlySpan を作成する場合は高速 • アセンブリのデータを ReadOnlySpan で読むだけなので非常に高速 コレクションリテラル導入以前から 存在する最適化

Slide 67

Slide 67 text

Span ReadOnlySpan の初期化の改善 エンディアンの問題で 1 byte の primitive type に限られていた コレクションリテラル導入以前から 存在する最適化 • .NET 8 時点で byte / sbyte / bool の定数の ReadOnlySpan を作成する場合は高速 • アセンブリのデータを ReadOnlySpan で読むだけなので非常に高速

Slide 68

Slide 68 text

Span ReadOnlySpan の初期化の改善 • .NET 9 ではすべての primitive type に同種の最適化が適用され高速に • RuntimeHelpers.CreateSpan を用いる事で解決される運びとなった https://github.com/dotnet/runtime/issues/60948

Slide 69

Slide 69 text

過去の資料 C# 13 / .NET 9 の新機能について語っています https://speakerdeck.com/nenonaninu/net-9-noxin-ji-neng-rc-1-shi-dian

Slide 70

Slide 70 text

References • https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-9/ • https://maoni0.medium.com/dynamically-adapting-to-application-sizes-2d72fcb6f1ea • https://learn.microsoft.com/en-us/archive/msdn-magazine/2005/may/net-framework-internals-how-the- clr-creates-runtime-objects • https://github.com/dotnet/runtime • https://github.com/Maoni0/mem-doc • https://speakerdeck.com/nenonaninu/dot-net-8-deji-ding-deyou-xiao-ninatuta-dynamic-pgo-nituite