C# Kaigi 2026「C#の現在地 進化の歴史と、AI時代の.NET Everywhere」, MessagePack for C# v4 preview.1

C# Kaigi 2026が先月開催されました!この振り返り記事は、スライドで9月末までにはリリースすると告知していたMessagePack for C# v4のプレビューリリースと合わせて、9月中に書く予定だったのですが、結局リリースできたのは昨日という有様だったので今の今までに遅れてしまいました、が、なんとかリリースもできましたし、これで無事振り返れます。

キーノート!記念すべき第一回において、なんという大役!もちろん、喜んで受けさせていただきました。第一回ですし歴史は欠かせないだろう、そして勿論AIで変化していくこれからのことも欠かせないだろう、というわけで大枠はすぐに決まったんですが、ただたんに歴史を淡々と話しても面白くもないというか、別に自分が作ったわけでもないからWikipedia読み上げてるだけになるんちゃうんちゃうん。そういうのは嫌だなー、ということなので、肉付けに関しては工夫したつもりです。

さも原初から知ってるんですよ!みたいな風に話したわけですが、私が最初にC#に触れたのは2008年、C# 3.0からです。なので、コミュニティには、Visual C++の時代からずっとMicrosoftテクノロジーを使ってきて、C#も最初の最初から使ってきたんですよ、という歴戦の猛者みたいな人ばかりで、私としては恐縮しきり、みたいなところから入っていったのを思い出します。ところが気づいたら、私がC#では随分と古参みたいな雰囲気に……!なんだったら私より上の世代の人はC#から引退していった人が多く、実際、現役でコミュニティの前線でやってる人間としては疑いようなく古参側に……!

10年とか20年とか経てば、使う言語が変わるのは当たり前なんですよね。それが当たり前だというのに、私はずっと続けられているので大したものです。続けられた理由としては、仕事で使い続けられたというのが大きいかなー。当たり前ですが仕事で使わないものをプライベートでも使い続けるのは難しいですからね!そして、状況に作用されず使い続けられる環境を作れたことがとても大きい。つまり技術の決定権が完全に自分にあり、問答無用でC#を選択するということができるポジションを気づくのが、C#を続けるにおいて一番重要なことです、身もふたもない!もちろん、ちゃんと仕事として成立させるための能力や説得力が必要なので、そこを怠らず走り続けられたことも大事です!

そして、C#がちゃんと前線で使い続けることが魅力的な言語であり続けてくれたことというのが大きい。よかった。AI時代でも第一線で活躍し続けられますよ、ね……?そこはわかりませんが(わかりません!)、それなりにいいポジションとポテンシャルはあるとは思っているので、引き続きやっていくぞ!ということで。C# Kaigiのような情熱的な場もありますしね、そう、とても良かったです。それぞれのセッションは熱く、参加者も昔からの人もいれば新しい人もいてやってくぞという気概に満ちていました。イベントは最初の一歩の主催が最も大変なことで、とても感謝です。そして、また続いてくれると嬉しいです。

MessagePack for C# v4 preview.1

ここずっと延々と作っていたのですが、無事なんとかpreview.1のリリースにこぎつけました!

previewの更に前の、最初のプロトタイプで大炎上が発生して苦しい出発だったのですが、なんとかやり切ることができました。普通だったら心が折れてると思うので、自分がどういうポジションから言ってるのかを認識して、きちんと他人にはリスペクト持って発言して欲しいところですね。

と、それはともかくとして、謳い文句は

  • Performant by default
  • Secure by default
  • Schema-resilient by default

です。そう、セキュリティです。昨今のAIによる攻撃は死ぬほど激しくて、旧来の造りでの継ぎ接ぎパッチではやってられない状態になっています。これは今思ったことではなくて、5月頃にMythosをこっそり使わせてもらってレビューを入れたときに思ったことです。Mythosのレビュー内容が、というよりも、それに対してAI生成で雑パッチをが当たった時に、これを延々と繰り返すのは無理だな、と。そしてAIによる分析力を活かしたパフォーマンス向上が見込めそうだというのも合わせて、パフォーマンスとセキュリティを向上させることで、ある程度の破壊的変更が許される(許して欲しい!)v4が始動できそうだ、と。

で、パフォーマンスについて。性能はデータによって変わってくるところもあるのですが、大きめのPOCOのシリアライズで見てみるとこんな感じです。

文句なく速い、です!シリアライザってかなり縮めるの難しくて、ドングリの背比べみたいな性能バトルになりがちなのですが、圧倒的にちぎってます。話題になったCapcomのRE EngineのREDoxのシリアライザーと比べても全然高速です。

色々な秘訣があるのですが、一番代表的なところではシリアライザーのインターフェイスの変更(これが破壊的変更の最大の要因)があります。

public interface IMessagePackFormatter<TWriteBuffer, TReadBuffer, T>
    where TWriteBuffer : struct, IWriteBuffer, allows ref struct
    where TReadBuffer : struct, IReadBuffer, allows ref struct
{
    void Initialize(MessagePackFormatterResolver resolver);

    void Serialize(ref TWriteBuffer buffer, ref SerializeState state, T value);

    void Deserialize(ref TReadBuffer buffer, ref DeserializeState state, ref T value);
}

v3 ではバッファの型は固定で、すべてのフォーマッターがref MessagePackWriterと ref MessagePackReader を受け取っていました。しかしシリアライザーの入出力は多様です。Span<byte>、IBufferWriter<byte>、ReadOnlySpan<byte>、ReadOnlySequence<byte>、その他色々。教科書的なオブジェクト指向の答えは、それぞれを抽象化する IWriter / IReader インターフェイスを用意することです。しかしシリアライザーのバッファ操作ではそれは選択肢になりません。バイト単位の操作ごとに仮想呼び出しが入るコストは大きすぎます。

そこで v3 の MessagePackWriter と MessagePackReader は、PipeReader/PipeWriterを想定した IBufferWriter<byte> と ReadOnlySequence<byte> を第一級の表現として扱う struct でした。その結果、ひとつの struct があらゆる入出力の形を扱わなければならなくなりました。struct は肥大化し、ロジックは複雑になり、if 分岐だらけになりました。v4 ではバッファの型そのものをジェネリックパラメータとして渡すようになっています。入出力の型ごとに最短経路が用意され、すべての呼び出し箇所でバッファの型が具体的な struct になるので、devirtualization とインライン化が保証されます。シリアライザーはメッセージひとつにつきバッファ操作を何万回も呼ぶことがあるので、これはパフォーマンスに大きな影響があります。

さらに、個々のフォーマッターは Dynamic PGO の guarded devirtualization が確実に効くような形にしてあります。v4は、モダンなC#とモダンなランタイムが100%性能発揮するように調整しているというのが今までとの大きな違いです。.NETのバージョンアップによる最適化で偶然性能が良くなった!ではなくて、.NETのバージョンアップによる最適化が確実に効くように、調整しきっています。これをやりきれたのが、まさにAIの力で、何度も何度も出力されるasmを確認して、最適化が効く形に修正していったのです。

もう一つ、Deserialize(int)のベンチマーク結果を紹介します。プリミティブのシリアライズというのはそれなりにやる機会が多いのと、もう一つは対象が小さいことにより、シリアライザーのセットアップコストだけを純粋に計測できるというところが注目しているところです。

というわけで、圧倒的に速いんですが、それがちゃんと理由で見えるんですよね、命令数が少ないから速いという、当たり前のことです。機能を保ちながら命令数を削る、それが最適化です。

v4の細かい分析は以下のようになっています。色々書いてありますが、これ以上は削れないと思っています。何度も見直しましたが限界 of 限界です。

競合のNerdbank.MessagePackは、こうです。見た通りに無駄が多く、無駄通りの性能低下を招いています。

ところであともう一点、「lastEntry キャッシュ」について。これって何かというと、最後に使ったコンバーター(Converter<T>)を一つだけキャッシュしています。 これやるとベンチマークには効きます、単一のTでSerialize<T>とかDeserialize<T>を回すから、ルックアップコストが丸ごと省けます。でも、実際のアプリケーションでシリアライザーに渡ってくるT ってバラバラに決まってるんですよね。だから、こういうのはベンチマークハックであり、やるべきでないことだと思っています。しかも別にタダじゃないですしね、毎回分岐予測を招くので通常のケースでは性能低下するだけ、という。これ、System.Text.JsonもやってるというかSystem.Text.Json作ってる人がPRで持ち込んできてるんですよね、そういう実装入れるのは恥じるべき。


v4ですが、変わってないところもありますが、変わったところも多いので、ぜひ試して欲しいです。正式版は、大きな問題がなければ11月中旬~下旬に出したい予定です。せっかくC# 15のunionにも対応してますし、概ね.NET 11合わせができればいいなぁ、と。

そしてここで先に言っておきますこととして、オーガナイゼーションを移管する予定です。Cysharpオーガナイゼーションに移して、v4のメンテナンスは私とCysharpが行うことになります。v3に関しては、今までどおりのメンテナンス体制でセキュリティ問題へのパッチは継続する予定です。また.NET Foundationも継続です。オーガナイゼーションと開発主体は変更しますが、OSSであることに変わりはなく、ライセンスもMITのままです。安定的な開発継続に欠かせないことですので、ご了承ください。

Profile

Yoshifumi Kawai

Cysharp, Inc
CEO/CTO

Microsoft MVP for Developer Technologies(.NET)
April 2011
|
July 2027

X:@neuecc GitHub:neuecc

Archive