Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
CysharpのOSS群から見るModern C#の現在地
Search
Yoshifumi Kawai
November 18, 2024
Technology
46k
2
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
CysharpのOSS群から見るModern C#の現在地
Sansan x Cysharp
Yoshifumi Kawai
November 18, 2024
More Decks by Yoshifumi Kawai
See All by Yoshifumi Kawai
.NET for Android with Native AOT Built with Cursor
neuecc
0
5.6k
R3のコードから見る実践LINQ実装最適化・コンカレントプログラミング実例
neuecc
5
56k
.NETの非同期戦略とUnityとの相互運用
neuecc
3
56k
他言語がメインの場合のRustの活用法 - csbindgenによるC# x Rust FFI実践事例
neuecc
7
42k
Modern High Performance C# 2023 Edition
neuecc
4
16k
CEDEC 2023 モダンハイパフォーマンスC# 2023 Edition
neuecc
9
120k
C#11 による世界最速バイナリシリアライザー「MemoryPack」の作り方
neuecc
1
42k
Unityによるリアルタイム通信とMagicOnionによるC#大統一理論の実現
neuecc
1
520
Deep Dive async/await in Unity with UniTask(UniRx.Async)
neuecc
0
550
Other Decks in Technology
See All in Technology
The Django UUID Story - DjangoCon US 2026
pauloxnet
0
290
Claude Codeの体系的な理解と知識のフック
oikon48
6
5.4k
AIレビュー時代に必要なのは、SLOで引く撤退ライン
nobuoooo
0
140
型落ちシンクライアント端末のPoEモジュールを自作したかった話
logica0419
0
480
「面白い!」を信じ抜け。激動の時代を貫く、オンリーワン・エンジニアの条件
kizawa2020
0
280
DMMブックスのNext.js化を加速させるAI活用 / Migrating DMM Books to Next.js with AI
kentarom
0
130
医療の現場を変革に挑戦した半年間の軌跡 - PythonとAIで現場を変える / From Code to Care
soudai
PRO
1
630
Claude Codeで開発以外の業務も爆速化しよう!
minorun365
PRO
9
8.2k
VPCでもFloatingIPを使いたいんだ
y_kotani
1
110
ブラウザで変わるID連携(OAuth/OIDC Numa (Immersion) Workshop 2026)
oidfj
PRO
0
320
パスキーでドライブする アカウント統合(OAuth/OIDC Numa (Immersion) Workshop 2026)
oidfj
PRO
0
330
atproto spaces概要
yamarten
0
110
Featured
See All Featured
Put a Button on it: Removing Barriers to Going Fast.
kastner
60
4.5k
CoffeeScript is Beautiful & I Never Want to Write Plain JavaScript Again
sstephenson
162
16k
How to train your dragon (web standard)
notwaldorf
97
6.8k
Side Projects
sachag
455
43k
Chasing Engaging Ingredients in Design
codingconduct
0
280
Crafting Experiences
bethany
1
270
Amusing Abliteration
ianozsvald
1
260
Conquering PDFs: document understanding beyond plain text
inesmontani
PRO
4
3k
End of SEO as We Know It (SMX Advanced Version)
ipullrank
3
4.4k
Building a Scalable Design System with Sketch
lauravandoore
463
34k
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
28
3.6k
Helping Users Find Their Own Way: Creating Modern Search Experiences
danielanewman
31
3.3k
Transcript
CysharpのOSSから見る Modern C#の現在地 2024-11-18 Cysharp x Sansan Event Yoshifumi Kawai
/ Cysharp, Inc.
About Speaker 河合 宜文 / Kawai Yoshifumi / @neuecc Cysharp,
Inc. - CEO/CTO 株式会社Cygamesの子会社として2018年9月設立 C#関連の研究開発/OSS/コンサルティングを行う Microsoft MVP for Developer Technologies(C#) since 2011 CEDEC AWARDS 2022エンジニアリング部門優秀賞 .NETのクラスライブラリ設計 改訂新版 監訳 50以上のOSSライブラリ開発(UniRx, UniTask, MessagePack C#, etc...) C#では世界でもトップレベルのGitHub Star(合計40000+)を獲得
https://github.com/Cysharp/ GitHubスター総数30000以上!
上位10個(合計27000スター)を 見ていきます! 技術的に見るべきところが多いとか先端度が高いとか はスター数とはあまり関係ないのですが、今回は分か りやすくTOP10ランキングからの紹介でいきます!
UniTask
UniTask (★8308) Unity用のカスタム非同期ランタイム https://github.com/Cysharp/UniTask 独自の非同期処理システムを実装 TaskはThreadPoolで動くランタイム UniTaskはゲームエンジンに特化し ゲームループ上で動くランタイム Task、のようでTaskじゃ ない独自の生態系
非同期ランタイムの差し替え await可能な型 GetAwaiter()を実装する async関数の戻り値にできる型 AsyncMethodBuilderを実装する(C# 7.0) コンパイラ生成のコードがカスタム実装のBuilderを呼ぶようにな るため、既存のTask用のBuilderを完全にバイパスできる これはたまによくやるパターン ちょっと(かなり)珍しい
非同期ランタイムの差し替え await可能な型 GetAwaiter()を実装する async関数の戻り値にできる型 AsyncMethodBuilderを実装する(C# 7.0) コンパイラ生成のコードがカスタム実装のBuilderを呼ぶようにな るため、既存のTask用のBuilderを完全にバイパスできる 全体をUniTaskで統一する、という世界観をユーザーに強いるこ とが出来れば、性能面/利便性でフレームワークに完全に特化した
カスタムの非同期ランタイムをユーザーに提供することができる このカスタマイズ性は言語的にもかなりイ ケてる部類に入る!C#すごいイイ! まぁ、現状、実用的な形でそれを実践して幅広く 利用されているランタイムはUniTaskぐらいしか ありませんが……(逆にUniTaskすごい)
MagicOnion
MagicOnion (★3869) C# CodeFirst API/Realtime RPC https://github.com/Cysharp/MagicOnion gRPC上に構築されたC#専用RPC クライアントはUnityにも対応 gRPC標準のProtocol
Buffersではなく MessagePack for C#を使用することで .proto不要のCode First RPCを実現 実はかなり歴史が古い async対応の独自型テクニックも利用して手触 り向上(Task<UnaryResult<T>> よりも UnaryResult<T>のほうが書きやすい
2016-08-23 gRPC 1.0 MagicOnion 0.1.0 2016-12-11 2019-09-20 first stable release
of gRPC for .NET googleによるC#実装も含む わずか4か月後に公開 (開発開始はgRPC 1.0リリース直後から) 更に4か月後の2017-04-26にゲームをリリース してMagicOnionがiOS/Androidで実稼働 その数十年後にようやく MicrosoftがgRPCの価値に気付く
RPC Generation 言語の違うREST Response型を別々 に書く APIクライアント を手書きする (メンテナンスも 大変で気合が必要、 何気に多い)
中間IDLを書く そこからクライア ント・レスポンス 型自動生成 (←を嫌う時によ くある構成、これ が最先端だと多く の人が勘違いして いる) サービスを普通に 書く、そこからク ライアントをコマ ンドラインツール で自動生成、リク エスト・レスポン ス型は同一言語で 共有 サービスを普通に 書く、そこからク ライアントをコン パイル時Source Generatorで静的 生成、リクエス ト・レスポンス型 は同一言語で共有 普通のREST JsonSchema/gRPC MagicOnion v6 MagicOnion v7 MagicOnionは世代が遥か未来のRPC 世間は未だこの辺
RPC Generation 言語の違うREST Response型を別々 に書く APIクライアント を手書きする (メンテナンスも 大変で気合が必要、 何気に多い)
中間IDLを書く そこからクライア ント・レスポンス 型自動生成 (←を嫌う時によ くある構成、これ が最先端だと多く の人が勘違いして いる) サービスを普通に 書く、そこからク ライアントをコマ ンドラインツール で自動生成、リク エスト・レスポン ス型は同一言語で 共有 サービスを普通に 書く、そこからク ライアントをコン パイル時Source Generatorで静的 生成、リクエス ト・レスポンス型 は同一言語で共有 MagicOnionはC# RPCの決定版となるべく、 手触りが良いことを最重要視して、現在も 最前線で開発を進めています……!
MemoryPack
MemoryPack (★3345) The fastest serializer in C# https://github.com/Cysharp/MemoryPack C#に特化した実装 +
バイナリ仕様により究極の速さを実現 MessagePack for C#(★5795)の限界を言語特化にすることで超えた 最近、.NET Foundationに参加しました! 実績(Visual StudioやSignalR, Blazorなど での採用)と相互運用性、実装の手堅さで はMessagePack for C#、エクストリーム パフォーマンスという点では MemoryPackという使い分け(?)
パフォーマンスの秘訣 Source Generator 旧来のシリアライザーの高速化はIL.Emitが定番だったが いち早くSource Generatorに特化した Span<T>, IBufferWriter<T>,ReadOnlySequence<T> モダンI/O型の標準採用 System.Runtime.CompilerServices.Unsafe
Span<T>と合わせて メモリを直接読み書きする あとは徹底的なアローケション避けや、 分岐の最小化、メソッド呼び出し回数 の削減など地道な努力……
R3
R3 (★2263) Reconstruction of Reactive Extensions https://github.com/Cysharp/R3 Rxベータ版からの超初期ユーザー(2011-)かつUnity版Rxである UniRx(2014- ★7098)の実装経験をもとに、Rxを現代的に再構築
多くのプラットフォーム対応(Unity, Godot, Avalonia, WinUI3, etc...) より高機能(FrameOperators, AwaitOperators) より分かりやすく(async/await integration) よりアクティブなメンテナンスビリティ コードの複雑さから、dotnet/reactive既に ほとんどメンテ不能になってる……
TimeProviderの全面採用 そもそもR3は存在そのものがModern C#なのだが? SystemClock.Now的な利用だけではなく、 DateTimeOffset, TimeZoneInfo, Stopwatch, Timer の抽象層になっている 標準で用意されてるFakeTimeProviderも便利
TimeProvider in R3 カスタムTimeProvider作りまくり 超大量のメソッド内利用
Use Timestamp DateTimeOffsetやStopwatchそのものは使わず、 GetTimestampで高解像度タイマー(Windowsで はQueryPerformanceCounter)から時間を取得 GetElapsedTimeでタイムスタンプ間 から経過時刻のTimeSpanを算出する
Stopwatch.GetElapsedTime from .NET 7 DateTime.UtcNow – nowは使わない! ある地点からの経過時間(TimeSpan)を取得するのに日付はいらない Stopwatch.StartNew(heap allocation)もいらない
オレオレValueStopwatchもいらない 開始地点のlongをGetTimstampで取得して保持するだけ Stopwatch.GetTimestampは昔からあるけれど、2点のtimestampから TimeSpanを取得するGetElapsedTimeは.NET 7から……! (.NET 7以前でもMicrosoft.Bcl.TimeProviderを入れて TimeProvider.System.GetElapsedTimeを使うというハックもある!) 日付の取得 is not FREE ただのlongなのでStopwatchよりむしろ取り回しがいい
ZString
ZString (★2082) Zero Allocation UTF8/UTF16 String Builder https://github.com/Cysharp/ZString 文字列周りの処理は、当時の.NETは多くの無駄があった 例えば数値型のStringBuilder.Appendでは
netstandard2.0 -> 文字列化してからバッファに書き込み netstandard2.1 -> ISpanFormattable.TryFormatで直接書き込み Formatメソッドもobject argsによるboxingが避けられない、など Utf8関連でも文字列化->UTF8エンコードといった多くの無駄が発 生していた(.NET Core 3.1辺りで改善されて、doubleはGrisu3と いったアルゴリズムで直接書き込むようになった)
Improvement Interpolated Strings C# 10.0でInterpolated Stringsが大幅改善された コンパイル時にInterpolatedStringHandler経由で処理する+適切な 一時バッファ量をコンパイル時推測することで、Zero Allocation UTF16
String Builderが実現されている UTF16関連でのZStringのアドバンテージはあまりない UTF8周りはまだ未熟のため、新世代のライブラリを作った Utf8StringInterpolation(★157) https://github.com/Cysharp/Utf8StringInterpolation Utf8StreamReader(★213) https://github.com/Cysharp/Utf8StreamReader それつまりBetter ZString...... UTF8直読み書きはパフォー マンス上最重要!が、需要 は少ないみたい……
ConsoleAppFramework
ConsoleAppFramework (★1656) Source Generator based CLI Framework https://github.com/Cysharp/ConsoleAppFramework 最新版(v5)は大幅な破壊的変更によりSource Generatorベースに完
全に作り変えられた ライブラリ自身も含めた追加の依存性ゼロ ゼロリフレクションによるNative AOT完全対応 ゼロアロケーションによる完全最適化した手書きと同じ実行速度 コールドスタートからの 実行速度も圧倒的!
マクロ的Source Generator メソッド呼び出しの中 身から生成する 多くのSource Generatorは属性ベースで動作します が、RustにはAttribute-like macrosとFunction-like macrosという分類があり、これはfunction-likeなス タイルとして着想を得た
MasterMemory
MasterMemory (★1534) Readonly In-Memory Database https://github.com/Cysharp/MasterMemory マスターデータ(実行中変動が(ほぼ)ないデータ)の特性から、 起動時にインメモリに全て読み込むことで結果的に省メモリ+高 速動作が可能になるという発想のデータベース Roslynを使ったコード生成が(当時と
しては)秘訣だったのですが、今と なっては手法が古いのでSource Generatorベースに改修中......
MessagePipe
MessagePipe (★1438) High performance in-memory messaging https://github.com/Cysharp/MessagePipe DIベースのインメモリイベント処理フレームワーク 速い!それとasync対応も嬉し い!DI対応はフレームワーク
(ASP .NET とかVContainerを使っ てる状態だとかなり嬉しい!
DI First Microsoft.Extensions.DependencyInjection よくも悪くも、今の .NET のフレームワークは全てがDIベース あらゆるフレームワークが Generic Host(Microsoft.Extensions.Hosting) の上で構築されているため
そこに全面的に乗っかるパターン と、あえてそこから逸脱する (ConsoleAppFramework v5)と いった試みもしている
Ulid
Ulid (★1334) Sortable GUID alternative https://github.com/Cysharp/Ulid 先頭48bitがTimestampなので ソート可能という性質を持つ データベースのIDとして使う場合などに、GUIDだ とランダム配置されるため性能劣化に繋がるが、
UlidだとInsert時間で固まるため性能的に優位 (Optional)単独で生成時間が分かるのも便 利といえば便利(ユーザーに露出すると問 題がある場合もあるので注意) 実用的にはGUIDのランダム性 とほとんど遜色なし
Guid.CreateVersion7(.NET 9) UUID Version 7 - RFC9562(2024-05) GUIDはUUIDのMicrosoft方言で、ほぼイコール Ulidの良くなかったところ UUIDと互換性のない文字列表現
Guidと型が違う(当然だが既存システムとの相互運用性が劣る) CreateVersion7の良いところ よくもわるくもGuidそのもの CreateVersion7の良くないところ 生成速度がGuid + Timestampで先頭上書きなのでやや劣る 性質が違うのに型が同じとい うのは厳密には良くない。が、 現実的な利便性では同じこと のほうがプラス。 つまり、私的には.NET 9以降な らGuid v7のほうがお薦めです!
public static class GuidEx { private const byte Variant10xxMask =
0xC0; private const byte Variant10xxValue = 0x80; private const ushort VersionMask = 0xF000; private const ushort Version7Value = 0x7000; public static Guid CreateVersion7() => CreateVersion7(DateTimeOffset.UtcNow); public static Guid CreateVersion7(DateTimeOffset timestamp) { // 普通にGUIDを作る Guid result = Guid.NewGuid(); // 先頭48bitをいい感じに埋める var unix_ts_ms = timestamp.ToUnixTimeMilliseconds(); // GUID layout is int _a; short _b; short _c, byte _d; Unsafe.As<Guid, int>(ref Unsafe.AsRef(ref result)) = (int)(unix_ts_ms >> 16); // _a Unsafe.Add(ref Unsafe.As<Guid, short>(ref Unsafe.AsRef(ref result)), 2) = (short)(unix_ts_ms); // _b ref var c = ref Unsafe.Add(ref Unsafe.As<Guid, short>(ref Unsafe.AsRef(ref result)), 3); c = (short)((c & ~VersionMask) | Version7Value); ref var d = ref Unsafe.Add(ref Unsafe.As<Guid, byte>(ref Unsafe.AsRef(ref result)), 8); d = (byte)((d & ~Variant10xxMask) | Variant10xxValue); return result; } public static DateTimeOffset GetTimestamp(in Guid guid) { ref var p = ref Unsafe.As<Guid, byte>(ref Unsafe.AsRef(in guid)); var lower = Unsafe.ReadUnaligned<uint>(ref p); var upper = Unsafe.ReadUnaligned<ushort>(ref Unsafe.Add(ref p, 4)); var time = (long)upper + (((long)lower) << 16); return DateTimeOffset.FromUnixTimeMilliseconds(time); } netstandard2.0向けにこういうの 作っちゃって、Guid v7に統一する のを選ぶかも Timestampの取得メソッドは標準 にはないので用意してもいいかも
Endianに注意 GUIDの内部表現はLittleEndian ToByteArray(bool bigEndian)やTryWriteBytes(bool bigEndian)で 出力時のエンディアンを決定するという方式になっている 無指定の場合はリトルエンディアンになる v7はデータベースに格納する場合はBig推奨 タイムスタンプ部でSortするからバイナリ表現としてBigが適切 C#側のデータベースドライバーに注意
GUIDがBigで書き込む設定になっていないと逆に性能劣化 mysqlconnector-netは接続文字列でGuidFormat=Binary16にする (デフォルトがそうなのでmysqlconnector-netは基本的に大丈夫)
Endianに注意 GUIDの内部表現はLittleEndian ToByteArray(bool bigEndian)やTryWriteBytes(bool bigEndian)で 出力時のエンディアンを決定するという方式になっている 無指定の場合はリトルエンディアンになる v7はデータベースに格納する場合はBig推奨 タイムスタンプ部でSortするからバイナリ表現としてBigが適切 C#側のデータベースドライバーに注意
GUIDがBigで書き込む設定になっていないと逆に性能劣化 mysqlconnector-netは接続文字列でGuidFormat=Binary16にする (デフォルトがそうなのでmysqlconnector-netは基本的に大丈夫) PostgreSQLも、npgsqlの標準のconverterがそうなってるので大丈夫。 Microsoft SQL Serverは悲しいことにダメで議論進行中。 https://github.com/dotnet/SqlClient/discussions/2999 新しいドライバのほう(Microsoft.Data.SqlClient)でダメなので、古いドライ バ(System.Data.SqlClient)はなおダメでしょう。
ZLogger
ZLogger (★1286) Zero Allocation Text/Structured Logger https://github.com/Cysharp/ZLogger 速い&デフォルト設定が速い!
Extreme Interpolated String カスタムInteprolatedStringHandlerにより、 String(UTF16)を介さずにUTF8のロギング用バッ ファーに直接書き込みすることでゼロアロケー ション&超高速化
Conclusion
C#の可能性を切り開いていく 言語は進化する、ではライブラリは? 可能性は両輪で切り開いていかなければならない 言語が進化するなら、ライブラリも必然的に進化する 時には破壊的変更も厭わない……! 使いやすさとパフォーマンスを引き出す そのための新しいC#であり、新しい.NET C#をあらゆる言語においてベストな環境にすることに Cysharpは貢献しています……!
None