(Translated by https://www.hiragana.jp/)
neue cc - 2016-12

2016ねんかえ

かえる、のもかい今年ことしは、ものすごくC#を技量ぎりょう向上こうじょうしたがします。いやほんと。わたし結構けっこうとしとったかんがあるのですが(昨日きのう誕生たんじょうで33さいでした!)、まだグッと成長せいちょうできるくちのこってたんだなぁとおもうと大変たいへんうれしいはなしです。正直しょうじき今年ことしはあまりいニュースはなかったのですが、自分じぶんのメインのじく自己じこ成長せいちょう実現じつげんできたというのは、つぎのステップ頑張がんばろうってになれます。

C#

プログラミングって、ある程度ていどはパターンがあって、このシチュエーションにはこれをてはめて、こういうふうてていけばてる、みたいな手札しゅさつおおさがつよさ(?)みたいなところがあるとおもってるんですが、ここ2ねんほどわたし自身じしんのデッキはわり安定あんていしていたんですよね。言語げんごやフレームワークのアップデートにしたがってえたり、のライブラリを手札しゅさつを、アイディアをやすというのは随時ずいじやっていってましたが、おおきくわるようなことはなかったなあ、と。言語げんごがアップデートされると、そりゃ当然とうぜん手法しゅほうおおきくわるんですが、くもわるくもC#は安定あんていはいっていて、ぶっちけそんなわってないし、つぎのC#もたいしてわらないですしね。

って状況じょうきょうだったんですが、今年ことしはガラッとかたかんがかたわりました。もちろん、使つかつづけている手札しゅさつもいっぱいありますが、新規しんきはいってきた要素ようそもとてもおおくて。そのおかげで、APIの表現ひょうげんりょく大幅おおはばがりました。わせの問題もんだいでもあるので、手札しゅさつおおいと、やれることのはばやAPIの表現ひょうげんりょく爆発ばくはつてきがっていくので非常ひじょういことです(ぎゃく手札しゅさつすくないひとつくるAPIは窮屈きゅうくつだったりするというのはありますね、そういうのみると慢心まんしんしてるかんじだなあ、とかおもったりはします)

わった要因よういんは2つあって、ひとつは、今年ことしはパフォーマンスを極限きょくげんまでもとめたコードを色々いろいろいたから。ブログをあさるとUnityでのボクシングのころかたあるいはラムダしきにおけるえないnewの見極みきわかたUnityにおけるコルーチンのしょうメモリと高速こうそくについて、あるいはUniRx 5.3.0でのその反映はんえいUniRx 5.4.0 - Unity 5.4対応たいおうとまだまだ最適さいてきと、UniRx継続けいぞくアップデートはいつもあたらしいことをかんがえたり、導入どうにゅうしたりするきっかけになっています。UniRxも今年ことしはGitHubで1000Starえをたしたり、スーパーマリオラン(5000まんダウンロード!)に採用さいようされていたりと、ひとつのやまえたかんじはあります。

個人こじんてきにブレークスルーだったのはLINQ to GameObject 2.1 - 手書てが列挙れっきょによる性能せいのう向上こうじょう追加ついかけいをより使つかいやすくで、あらためてLINQ、そしてパフォーマンスとは、にかんして見直みなおすきっかけになりました。そしてZeroFormatter - C#の最速さいそくかつ無限むげんだい高速こうそくな .NET, .NET Core, Unityようシリアライザーで、集大成しゅうたいせいとして結実けつじつしました。いやぁ、大変たいへんだった。ほんと大変たいへんだった、わってみればあっさりってもしなくもないんですが、いやぁ、大変たいへんだった……。シリアライザなんてれた群雄割拠ぐんゆうかっきょ代物しろものおもってましたが、性能せいのうめんでもまだまだ全然ぜんぜん追求ついきゅうできるはばあったんだというのはおどろきで。意外いがいなかまだやれることは無限むげんにある。C#もまだまだ限界げんかいむかえてない。

性能せいのう最大さいだい機能きのうだ、というのは勿論もちろんなのですけれど、究極きゅうきょくてきにそれを実現じつげんするためにはあたらしいアイディアを大量たいりょう投下とうかしなきゃいけなかった。いままで自分じぶんはいかにヌルいコードをいてたんだ、と痛感つうかんさせられました。また、そんな性能せいのう追求ついきゅうギプスのおかげ沢山たくさん手札しゅさつれられて、それは視野しやひろがりをもたらして、ただたんに性能せいのうのために、というだけじゃなくかたひろがりをれられたとおもってます。

めてやることにはとても意義いぎがある。ぎゃくに、そこまでしなければれられないものもある。手札しゅさつやすのに言語げんご浮気うわきするってのもわるいことではないですが、そのまえまえのことをめてみるってのもいいんじゃないのってのはとってもおもいます。nullがどうこうとかってるまえにC#どんだけけるのよ、みたいな。みたいな?

技術ぎじゅつてき負債ふさいとのかた

技術ぎじゅつてき負債ふさいって、優秀ゆうしゅうなエンジニアがしっかりかんがえれば発生はっせいしない。わけではないんですよね。コードなんてだれいても、いた瞬間しゅんかんから腐敗ふはいはじまっていて、アプリケーションとしてローンチするまえから負債ふさいになっている場合ばあいすらある。そして、出来できないエンジニアのつく負債ふさいよりも、むしろ出来できるエンジニアのつく負債ふさいのほうがいたかったりする。JavaScript界隈かいわいでよくくような、あたらしい技術ぎじゅつをいっぱいれました、でももう時代遅じだいおくれです!みたいなのは典型てんけいですが(これも普通ふつうよりちょっと出来できるエンジニアぐらいのほうがハマりやすいわな)、そんなんじゃなくても、だいなりしょうなり腐敗ふはいかかえてきてるわけです。

永遠えいえんかがやくコードなんて存在そんざいしないからこそ、むしろいかにてるかに腐心ふしんするほうがい。もちろん、わたしくものだって例外れいがいじゃあなくて、ゴミはつくってしまうのね。べつにゴミだとおもってつくるわけじゃなくても!ダメだとづいたら、しょうがないので焼却しょうきゃくする。これがね、自分じぶんつくったコードなら躊躇ちゅうちょなくてられる。てたさいのカバーもなんとかできる、こともある(できないこともある、ひどぅぃ)。けれど他人たにんつくったもののあつかいはとてもむずかしい。そもそも他人たにんいたものをジャッジするのがむずかしい!自分じぶんいたものを、あぁ、アイディア自体じたいがゴミでダメですね、とてれても、他人たにんのものをただしく判定はんていするのはむつかしいんだなあ。いや、現在げんざいにたいしてダメかかの判定はんてい簡単かんたんですけれど、未来みらい判定はんていをするのがむつかしい。

自分じぶんいたものだと未来みらいえるんですよね、このアイディアの延長線えんちょうせんじょうなにがあるか想像そうぞうがつく、未来みらいがないことがえたとき、やめましょう、てましょう、になる。けれど、他人たにん未来みらいはわからなくて、いまはまだまだだけど、もうすこしやってりゃあなんとかなるかもしれない……。とかおもっちゃうわけです。期待きたいして。あるいはをつむって。実際じっさい大抵たいていはそんなことはなくて、ダメなもんはダメだったりするわけですが。

そんりするのがむずかしいのと一緒いっしょで、そりゃうまくできりゃあいんですが。というかうまくできなきゃあダメなんですが。傷口きずぐち消毒しょうどく誤魔化ごまかしてないで、腐食ふしょくすすまえとさなきゃ本当ほんとうにダメで。くさった土台どだいのうえでいくら技巧ぎこうらしても、みにく延命えんめいさくで、なんの解決かいけつにもなってないというか、むしろただの時間じかん浪費ろうひなんですよね。いやはや。

いずれにせよ、おごった気持きもちでかれたものはダメですねぇ。「よくできているのにどうしょうもなくダメなプログラム」とはぞやか、というのをかんがなおすきっかけになりましたし、そうしてかんがなおすことは自分じぶんかた変化へんかにもつながりました。自分じぶん自身じしんね、そういうのしょいちゃってたりやっぱしてしまうわけで。

仕事しごと

というわけで技術ぎじゅつてき負債ふさい返却へんきゃく、じゃないですが、今年ことし後半こうはんは、意識いしきてきに、問題もんだい技術ぎじゅつ解決かいけつするというところにフォーカスしていました。結構けっこうね、状況じょうきょう余裕よゆうじゃないんですが、なんとかして解消かいしょうしなければならない!

ZeroFormatterを起点きてんに、まだ完成かんせいのものでMagicOnion - gRPC based HTTP/2 RPC Streaming FrameworkMasterMemory - Embedded Readonly In-Memory Document Databaseというのを用意よういしています。

現状げんじょうをクソだというのはイージーなんですが、なんとか維持いじしつつも解決かいけつさせるってのは結構けっこうむずかしくある。アイデアというのは複数ふくすう問題もんだい一気いっき解決かいけつするものであるとはよくったものですが、実際じっさい、これらの導入どうにゅうによってかかえている問題もんだいをそれなりに解決かいけつできる。といいなぁ。

技術ぎじゅつ技術ぎじゅつ返却へんきゃくするってのは、くもわるくもですね。とくに、わたし自身じしんがCTOという立場たちばでそれやってるのは、結構けっこうキワキワだとはおもってます。意識いしきしてのうみその9わりをコードにくようにしてるのは、ぎゃくのことはあまりかんがえてないってことですからねぇ。正直しょうじき、あんまいいことじゃあないし、来年らいねんおなじようにしたいとはおもわないというか、すべきではないとおもってますが、現在げんざい状況じょうきょうからすればこれが最善さいぜん、かな。とえらんでやってます。このあたりはしゃーない。もうすこしうまくやれりゃあいいのですけれど。

そんりのタイミングをいっしたとか、自分じぶん返却へんきゃくしなきゃいけないものを返却へんきゃくできなかったりとか、前期ぜんきであまり決断けつだんができてなかったというのはうーむ、といったところも多々たたありつつ。対外たいがいてきなプレゼンスにかんしてはよくやれたとおもってますし、そのあたりひとにはできないことをやってるとはおもいますが、それだけでいいとれない程度ていどには歯切はぎれのわるとしでした。

ゲームとか音楽おんがくとか

とんかつDJアゲ太郎たろうだけはアニメ全部ぜんぶました:) それ以外いがいはアニメもドラマもなにもかも完走かんそうできてないというかロクにちゃいない。ほんんでなければ漫画まんがてないんですが、うーん、なにかったかなあ。本日ほんじつのバーガーはテーマてきにはかった!色々いろいろなハンバーガーがあるし、あっていんだよ、というたりまえのようなたりまえ認識にんしきできて。

ゲームは、うーん、オーディンスフィア レイヴスラシル今年ことしでしたか、かった。あとスーパーマリオランはレート3000、ブラックコインコンプぐらいにはやりました。レートカンストはちょっと不毛ふもうかんあるので、いったんそろそろいいかなかんもありますが。

音楽おんがくは、水曜日すいようびのカンパネラをよくいてましたねー、ジパングわたし鬼ヶ島おにがしまれてって傑作けっさくで。あと、つい先日せんじつ戸川とがわじゅん with Vampillia / わたしがこうホトトギスくてホクホク。

来年らいねん

年始ねんししばらくはひたすらシステムプログラミングですねー。きでやってていいってことにも、限度げんどが、頻度ひんどというものがあって、おおげさ大掛おおがかりなものを連続れんぞくしてつくらなきゃいけないってのは正直しょうじきシンドイ。ゆうて神経しんけいめっちゃ使つかうのよ。やるにしても、もうすこ間隔かんかくあけながらやりたいよぅ、というのも自業自得じごうじとくなんでしょーがない。

というわけかで、去年きょねん目標もくひょうであったグラフィックプログラミングはちっとも前進ぜんしんしませんでした。今年ことしはVRにもしっかりしたかったんですが、あまりやれてないですね、まぁそうしたグラフィックプログラミングも、VRも、あと最近さいきん興味きょうみあるのはディープラーニングも、ゲームをリリースするまではおあづけ。

というわけで、リリースしましょう、ってことですね!

ASP.NET Coreを利用りようしてASP.NET Coreを利用りようしないMiddlewareのつくかた

今回こんかい記事きじASP.NET Advent Calendar 2016けのものとなります。最終さいしゅうとくくつもりもなかったのですが、たまたま表題ひょうだいのような機能きのうつMiddlewareをつくったので、せっかくなのでいておくか、みたいなみたいな。

.NET 4.6でASP.NET Core

まぁ普通ふつうに.NET 4.6でASP.NET Coreのパッケージれるだけなんですが。べつにASP.NET Coreは.NET Coreでしかうごかせないわけではなくて、ちゃんと(?).NET 4.6でもうごきます。如何いかんせん.NET Coreがまだ環境かんきょうとして成熟せいじゅくしてはいないので、つよくLinuxでうごかしたいという欲求よっきゅうがなければ、まだまだWindows/.NET 4.6でうごかすほうが無難ぶなんでしょう。Visual Studioのサポートも2015だとちょっとイマイチだともおもっていて、私的してきには本格ほんかくてきつくしていくのはVisual Studio 2017ちです。だつWindowsとして、Linuxでホスティングするというシナリオ自体じたいにはかなり魅力みりょくてきおもっていますし、ライブラリをつくるのだったらいまだと.NET Core対応たいおう必須ひっすだとおもいますけれど。

Hello Middleware

Middlewareとはなんぞやか、というと、ASP.NET公式こうしきのMiddlewareのドキュメントればいですね。

image

Httpのリクエストをけつけて、レスポンスをかえす。ASP.NET Core MVCなどのフレームワークも、Middlewareの一種いっしゅうところのMiddleware3にあたる、パイプラインの終点しゅうてん位置いちする)となせます。このパイプラインのチェーンによって、事前じぜん認証にんしょうはさんだりロギングを仕込しこんだりルーティングしたりなど、機能きのうをアプリケーションにしていくことができます。

かんがかたも、実質じっしつてきなメソッドシグネチャもASP.NET Coreの前身ぜんしんOWIN同一どういつです。いまではOWIN自体じたい機能きのう周辺しゅうへんフレームワークは完全かんぜんととのっていて、ASP.NET Coreですべまかなえるようになっているので、あたらしくつく場合ばあいはASP.NET Coreのことだけをかんがえればいいでしょう。ぎゃくに、OWINで構築こうちくしたものをASP.NET Coreへ移行いこうすることはそうむずかしくないです

ASP.NET Coreのパッケージはいろいろあって、どれを参照さんしょうすべきかなやましいのですが、最小さいしょうのコア部分ぶぶんとなるのはMicrosoft.AspNetCore.Http.Abstractionsです。これさえあればMiddlewareがつくれます。

では、パイプラインの各部かくぶにフックするだけの単純たんじゅんなMiddlewareをつくりましょう!

public class HelloMiddleware
{
    // RequestDelegate = Func<HttpContext, Task>
    readonly RequestDelegate next;

    public HelloMiddleware(RequestDelegate next)
    {
        this.next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        try
        {
            Console.WriteLine("Before Next");
            
            // パイプラインの「つぎ」のミドルウェアをぶ
            // 条件じょうけん判定はんていして「ばない」という選択せんたくることもできる
            await next.Invoke(context);

            Console.WriteLine("After Next");
        }
        catch (Exception ex)
        {
            Console.WriteLine("Exception" + ex.ToString());
        }
        finally
        {
            Console.WriteLine("Finally");
        }
    }
}

注意ちゅういてんとしては、完全かんぜんに「規約きやくベース」です。コンストラクタのだいいち引数ひきすうはRequestDelegateをち(そののパラメータが必要ひつよう場合ばあいだい引数ひきすう以降いこうく)、public Task Invoke(HttpContext context)メソッドを必要ひつようがあります。ぎゃくに、それをたしていればどのようなかたちになっていてもかまいません。

この規約きやくベースなところは賛否さんぴあるかなぁ、というところですが(わたしはどちらかというと)、C#の言語げんご機能きのうとしてはしょうがないめんもあります。(自分じぶんでもこののフレームワークをなんつくった経験けいけんがあるところから理解りかいしているうえで)実装じっそうめんはなしをすると、この規約きやくもっと大事だいじなところは、コンストラクタのだいいち引数ひきすうでRequestDelegateをれるところにあります。そして、C#は具象ぐしょうがたのコンストラクタのかた制約せいやくれられないんですよね。なので、MiddlewareBaseとかつくってもあんま意味いみがなくて、ならもう全部ぜんぶ規約きやくベースで処理しょりしちゃおうって気持きもちはかります。

Invokeのメソッドシグネチャをpublic Task Invoke(HttpContext context, RequestDelegate next)にすることで、そうしたコンストラクタの制約せいやくける必要ひつようがなくなって、メソッドにたいするインターフェイスでC#として綺麗きれい制約せいやくをかけることは可能かのうになるんですが(わたしも、なので以前いぜんはそういうデザインをっていた)、そうなるとパフォーマンスじょう問題もんだいかかえることになります。Invoke(HttpContext context, RequestDelegate next)というメソッドシグネチャだと実行じっこうに"next"を解決かいけつしていくことになるのですが、これやるとどうしても、nextを解決かいけつするための余計よけいなオブジェクト(クロージャをつくるかそれよう管理かんりオブジェクトをあたらしくつくるか)が必要ひつようになりますし、階層かいそうもそのなかあいだそうはさむため、どうしてもいちふかくなってしまいます。

ミドルウェアパイプラインは構築こうちくにnextを解決かいけつすることができるわけで、そうした実行じっこうのコストを構築こうちくおさむことが原理げんりじょう可能かのうです。それが、コンストラクタでnextをれることです。C#をかした設計せっけいうつくしさ vs パフォーマンス。このMiddlewareチェーンはASP.NET Coreにおけるもっと最下さいかそうのレイヤー。この局面きょくめんではパフォーマンスをえらぶべきでしょう。じついチョイスだとおもいます。

最後さいごに、使つかいやすいように拡張かくちょうメソッドを用意よういしましょう。拡張かくちょうメソッドなのでnamespaceはあさめのところにおいておくと使つかいやすいので、そのあたり適当てきとうをつけましょう:)

public static class HelloMiddlewareExtensions
{
    public static IApplicationBuilder UseHello(this IApplicationBuilder builder)
    {
        // 規約きやくベースで実行じっこうにnewされる。パラメータがある場合ばあいはparams object[] argsで。
        return builder.UseMiddleware<HelloMiddleware>();
    }
}

Middlewareを使つか

つくったら使つかわないと動作どうさ確認かくにんもできません!というわけでホスティングなのですが、これもAspNetCoreのパッケージはいっぱいありすぎてよくわからなかったりしますが、「Microsoft.AspNetCore.Server.*」がサーバーをてるためのライブラリになってます。IISならIISIntegration、LinuxでうごかすならKestrel、コンソールアプリなどでのセルフホストならWebListenerをえらべばOK。今回こんかいMicrosoft.AspNetCore.Server.WebListenerきましょう。

class Program
{
    static void Main(string[] args)
    {
        var webHost = new WebHostBuilder()
            .UseWebListener()      // ホスティングサーバーをめる
            .UseStartup<Startup>() // サーバー起動きどうばれるクラスを指定してい
            .UseUrls("http://localhost:54321") // げるアドレスを指定してい
            .Build();

        webHost.Run();
    }
}

public class Startup
{
    // Configure(IApplicationBuilder app)というのも規約きやくベースで名前なまえ固定こてい
    public void Configure(IApplicationBuilder app)
    {
        // さっきつくったMiddlewareを使つかう
        app.UseHello();

        // この最下さいかそう匿名とくめいMiddleware(nextがない)をつくる
        app.Run(async ctx =>
        {
            var now = DateTime.Now.ToString();
            Console.WriteLine("---------" + now + "----------");
            await ctx.Response.WriteAsync(DateTime.Now.ToString());
        });
    }
}

れいによって規約きやくベースなところがおおいので、まぁ最初さいしょはコピペできましょう、しょーがない。これでブラウザでlocalhost:54321をたたいてもらえば、現在げんざい時刻じこく出力しゅつりょくされるのと、コンソールにはパイプラインかよってますよーのログがます。

image

基本きほんのHello Worldはこんなところでしょう、のち全部ぜんぶこれの応用おうようぎません。

ASP.NET Coreを利用りようしてASP.NET Coreを利用りようしない

さて、本題ほんだい(?)。現在げんざいわたしMagicOnionというフレームワークをつくっていて(まぁまぁうごいてますが、一応いちおうalpha段階だんかい)、うた文句もんくは「gRPC based HTTP/2 RPC Streaming Framework for .NET, .NET Core and Unity」。つまり……?gRPCというGoogleのつくっている「A high performance, open-source universal RPC framework」を下回したまわりで使つかいます。つまり、ASP.NET Coreは使つかいません。さよならASP.NET Core……。

gRPCは(.NET以外いがいでは)非常ひじょうがりをせていて、ググればいっぱい日本語にほんごでもおはなしつかるので、らないほう適当てきとう検索けんさくを。非常ひじょういものです。

gRPCはHTTP/2ベースで、しかもデータは基本きほんてきにはProtocol Buffersでやりりされているので、従来じゅうらいのエコシステム(HTTP/1 + JSON)からのアクセスが使つかえません。そこでgrpc-gatewayというプロキシをあいだはさむことで HTTP/1 + JSONでけてHTTP/2 + Protobuf にルーティングします。それによりSwaggerなどの便利べんりUIも使つかえて大変たいへんはかどるという図式ずしきです。素晴すばらしい!

grpc-gatewayは素晴すばらしいんですが、Pure Windows環境かんきょう使つかうのはおそらく無理むりがあるのと、MagicOnionではデータをZeroFormatterでやりりするようにしているので、そのまま使つかえません。残念ざんねんながら。しかし、とくにSwaggerが使つかいたいんで絶対ぜったいにgrpc-gatewayてきなものはしい。と、いうわけで、用意よういしました。ASP.NET Coreを利用りようして(HTTP/1 + JSON)、ASP.NET Coreを利用りようしない(HTTP/2 + gRPC/MagicOnion/ZeroFormatter)。

public class MagicOnionHttpGatewayMiddleware
{
    readonly RequestDelegate next;
    // MagicOnionのHandler(キニシナイ)
    readonly IDictionary<string, MethodHandler> handlers;
    // gRPCのコネクション
    readonly Channel channel;

    public MagicOnionHttpGatewayMiddleware(RequestDelegate next, IReadOnlyList<MethodHandler> handlers, Channel channel)
    {
        this.next = next;
        this.handlers = handlers.ToDictionary(x => "/" + x.ToString());
        this.channel = channel;
    }

    public async Task Invoke(HttpContext httpContext)
    {
        try
        {
            var path = httpContext.Request.Path.Value;

            // HttpContextのパスをgRPCのパスと適当てきとう照合しょうごうする
            MethodHandler handler;
            if (!handlers.TryGetValue(path, out handler))
            {
                await next(httpContext);
                return;
            }

            // BodyにJSONがやってきてるということにする(実際じっさいはFormからの場合ばあいなど分岐ぶんきがいっぱいでもっと複雑ふくざつですが!)
            string body;
            using (var sr = new StreamReader(httpContext.Request.Body, Encoding.UTF8))
            {
                body = sr.ReadToEnd();
            }

            // JSON -> C# Object
            var deserializedObject = Newtonsoft.Json.JsonConvert.DeserializeObject(body, handler.RequestType);

            // C# Object -> ZeroFormatter
            var requestObject = handler.BoxedSerialize(deserializedObject);

            // gRPCのMethodをリクエストを動的どうてきつくる
            var method = new Method<byte[], byte[]>(MethodType.Unary, handler.ServiceName, handler.MethodInfo.Name, MagicOnionMarshallers.ByteArrayMarshaller, MagicOnionMarshallers.ByteArrayMarshaller);
            
            // gRPCで通信つうしん、レスポンスをる(ZeroFormatter)
            var rawResponse = await new DefaultCallInvoker(channel)
                .AsyncUnaryCall(method, null, default(CallOptions), requestObject);

            // ZeroFormatter -> C# Object
            var obj = handler.BoxedDeserialize(rawResponse);

            // C# Object -> JSON
            var v = JsonConvert.SerializeObject(obj, new[] { new Newtonsoft.Json.Converters.StringEnumConverter() });

            // で、HttpContext.Responseにく。
            httpContext.Response.ContentType = "application/json";
            await httpContext.Response.WriteAsync(v);
        }
        catch (Exception ex)
        {
            // とりあえず例外れいがいはそのまんまドバーッとしておいてみる
            httpContext.Response.StatusCode = 500;
            await httpContext.Response.WriteAsync(ex.ToString());
        }
    }
}

こまかいところはどうでもいいんですが(あと一部いちぶ端折はしょってます、実際じっさいはもうすこ複雑ふくざつなので)、基本きほんてきながれはJSONをZeroFormatterに変換へんかん内部ないぶうごいてるgRPCと通信つうしん→ZeroFormatterをJSONに変換へんかん。です。見事みごとひだりからみぎにデータをながすだけー、のお仕事しごと、ですね!

MagicOnion本体ほんたい限界げんかいまでボクシングが発生はっせいしないように、ラムダのキャプチャなどにも使つかって、ギチギチにパフォーマンスチューニングしてあるんですが、このGatewayはそんなに使つかってません:) まぁ、もとよりふくすうかい変換へんかんはしってる、パフォーマンスさい優先ゆうせんのレイヤーではないから、いっかな、という。どっちかというとデバッグ用途ようとでSwaggerを使つかいたいがために用意よういしたようなものです。本流ほんりゅう通信つうしんはこのレイヤーをとおることはないので。

image

ちゃんとgRPCでもSwagger使つかえてめっちゃはかどる。

What is MagicOnion?

gRPCは.protoを記述きじゅつしてサーバーコードの雛形ひながたとクライアントコードを生成せいせいします。わたしはこのIDL(Interface Definition Language)のそうがあまりきじゃないんですね。そもそも、クライアントもサーバーも、ありとあらゆるそうをC#で統一とういつしているので、C#以外いがい考慮こうりょする必要ひつようがないというのもあるので。なので、C#自体じたいをIDLとして使つかえるように調整ちょうせいしたり、MVCフレームワークでいうフィルターが標準ひょうじゅんでないので、それをめるようにしたり、gRPCは(int x, int y, int z)のような引数ひきすうならべるようなかたができない(かならずRequestクラスを要求ようきゅうする!)ので、動的どうてきにそれを生成せいせいするようにしたりして、より自然しぜんにC#で使つかえるように、かつ、パフォーマンスも一切いっさい犠牲ぎせいにしない(中間なかまそうはいってるからオーバーヘッドとおもいきや、むしろプリミティブがた使つかえるようになったのでむしろもとのgRPCよりはやくなる)ようにしています。そもそもそしてUnityでも動作どうさ出来できるような調整ちょうせい/カスタマイズなどなどもみで、ですね。

それ以外いがいはなしZeroFormatterとなぞRPCについて発表はっぴょうしてきました。にてすこいてあります。もうすこ詳細しょうさいはなしは、完成かんせいしたときに……。

まとめ

.NET Coreを本格ほんかくてきに(プロダクション環境かんきょうで)使つかうということは、とく開発かいはつ環境かんきょうというてんでまだりないところがおおくて(project.json廃止はいしとかゴタついたところもあるし)、VS2017ちだと判断はんだんしています。しかし、ASP.NET Coreのフレームワークめんでは十分じゅうぶん完成かんせいしていて、問題もんだいないですね。なので、そちらから随時ずいじ移行いこうしていきたいという気持きもちでいます。

まぁ、とはいえ↑でいたとおり、ほとんどASP.NET Core自体じたいすら使つかわないんですが。うーん、そうですね、やっぱスタンダードなつくり(JSON API)をクロスプラットフォームを紳士しんしんでます、みたいなことやってるあいだに、世界せかいすごいスピードでまわってるんですよね。Microsoftはつねいちおそいとおもっていて、まぁ今回こんかいもやっぱそうですよね、というかんじで、世間せけん成熟せいじゅくしたころにやっとすようなスピードかんだとおもってます。ナデラでOSSでスピーディーなのかといったら、べつわたしはそうおもってないですね、スピードというてんでは相変あいかわらずだなぁ、と。むしろ「まさしくやろうとする」圧力あつりょくたかさに自分じぶんしばられてしまっているすらします。スタンダードだからとJSONでコンフィグ頑張がんばろうとしてやっぱダメでした撤回てっかい、みたいな。そういうのあんまくないし、そのあたり束縛そくばくから自由じゆうになれたときしんのスタートなんじゃないかな。

ともあれ、わたしはgRPCにベットしてるんで、ASP.NET Core自体じたいわりとどうでもおもってます、いまのところ。でもそれはそれとして、当然とうぜん補助ほじょてきに)使つかってく必要ひつようはあるんで、そういうときにちょいちょいと出番でばんはあるでしょう。

C#にける日付ひづけのシリアライズ、DateTimeとDateTimeOffsetの裏側うらがわについて

C# Advent Calendar 2016記事きじになります。なに毎年まいとしいてるんですよねー。今年ことしは、つい最近さいきんZeroFormatterというC#で最速さいそくの(本当ほんとうにね!)シリアライザをいたので、その動的どうてきコード生成せいせい部分ぶぶんにフォーカスして、ILGenerator入門にゅうもん、にしようかとおもってました。ILGeneratorでIL手書てがき、というと、くろ魔術まじゅつむずかしい!とおもってしまうけなのですが、のところべつに、かるとそれほどむずかしくはなくて(面倒めんどうくさい&デバッグしんどいというのはある)、しかし同時どうじにILGeneratorでいたからはやかったりやくったり、というのもなかったりします。大事だいじなのは、どういうコードを生成せいせいするのかと、全体ぜんたいでどう使つかわせるようなシステムにげるのか、だったり。とはいえ、その理想りそうてきなシステムをむための道具どうぐとしてILGeneratorによるIL手書てがきが手元てもとにあると、表現ひょうげんりょくはばひろがるでしょう。

シリアライザつくっておもったのは、Jilは(実装じっそうが)大変たいへんくできているし、SigilはIL生成せいせいにおいておおいに役立やくだ素晴すばらしいライブラリだとおもってます。まぁ、そういうのつくときって依存いぞんけたいので使つかわなかったけれどね……。

みたいなイイばなしをしようとおもっていたんですが、ちょっと路線ろせん変更へんこうでDateTimeについてということにします。えー。まぁいいじゃないですか、DateTimeだってふかいですし、IL手書てがきなんかよりずっと馴染なじふかいではないですか。役立やくだ役立やくだち。

DateTimeとはなんぞやか

シリアライズの観点かんてんからうと、ulongです。DateTimeとはulongのラッパー構造こうぞうたいというのが実体じったいです。ulongとは、Ticksプロパティのことをしていて、なのでたとえばDayをろうとすれば内部ないぶてきにはTicksから算出さんしゅつ、AddHoursとすればhoursをTicksに変換へんかんしたのち内部ないぶてきなulongをして、あたらしい構造こうぞうたいかえす。といったかたち内部ないぶてきにはなっています。それぞれのオペレーションは除算じょざんをちょっとやる程度ていどなので、かなり軽量けいりょうといってもいいでしょう。

つまり、DateTimeとはなんぞやかというのは、Ticksってなんやねん、というはなしでもある。

Ticksとは、100ナノセカンド精度せいどでの、0が0001/01/01 00:00:00から、最大さいだいが9999/12/31 23:59:59.999999までをす。ほほー。

// 0001-01-01T00:00:00.0000000
new DateTime(ticks: 0).ToString("o");
// 0001-01-01T00:00:00.0000001
new DateTime(ticks: 1).ToString("o");

// 3155378975999999999
DateTime.MaxValue.Ticks

DateTimeにはもうひとつ、Kindという情報じょうほう保持ほじしています。KindはUtcかLocalかなぞか(Unspecified)のさん

public enum DateTimeKind
{
    Unspecified = 0,
    Utc = 1,
    Local = 2
}

ふつーにDateTime.Nowで取得しゅとくするは、Localになっています、ので日本にっぽん時間じかんである+9:00されたれます。さて、このKindは内部ないぶてきにはどこに保持ほじされているかというと、Ticksと相乗あいのりです!ulong、つまり8バイト、つまり64ビットのうち62ビットをTicksの表現ひょうげんに、のこりの2ビットでKindを表現ひょうげんしています。なんでそういう構造こうぞうになっているかといえば、まぁ節約せつやくですね、メモリの節約せつやく。まー、コアライブラリなのでそういう使つかかたします、てきなにか。

Ticksプロパティ、Kindプロパティはそれぞれ内部ないぶデータを脱臭だっしゅうしたてくるので、そうしたTicks, Kindが相乗あいのりした内部ないぶデータをりたい場合ばあいはToBinaryメソッドを使つかいます。復元ふくげんする場合ばあいは、FromBinaryです。

// Ticks + Kind(long, 8byte)
var dateData = DateTime.Now.ToBinary();
var now = DateTime.FromBinary(dateData);

これで8バイトでDateTimeのすべてを表現ひょうげんできるので、これが最小さいしょうかつ最速さいそく手法しゅほうになります。あまり使つかうこともないとおもいますが。

さて、当然とうぜんZeroFormatterはそうしたToBinaryで保持ほじしてるんだよね!?というと、ちがいます!seconds:long + nanos:intという12バイト使つかった表現ひょうげんびょう+ナノびょう)にしています。これはProtocol Buffersの表現ひょうげん流用りゅうようしていて、うーん、一応いちおうクロスプラットフォームてきにはそのほうがいいかな、みたいな(でもいまかんがえるとべつにTicksでなにわるい、ってはする……失敗しっぱいした……)。そして、Kindはてています。シリアライズにToUniversalTimeでUTCに変換へんかんし、そのUTCののみシリアライズしています。

で、Kindは、わたしてていいとおもってます。一応いちおうMSDNのDateTime、DateTimeOffset、TimeSpan、および TimeZoneInfo の使つかというドキュメントにもありますが

DateTime データを保存ほぞんまたは共有きょうゆうするさい、UTC を使用しようする必要ひつようがあり、DateTime の Kind プロパティを DateTimeKind.Utc に設定せっていする必要ひつようがあります。

UTCかLocalか、なんていうだけのはシリアライズにまったむかいてないです。それだったらTimeZoneも保存ほぞんしないと意味いみがない。アメリカで復元ふくげんしたらどうなんねん、みたいな。なのでシリアライズという観点かんてんるとKindはナンセンスきわまりないです。これはDateTimeの設計せっけいわるいってはなしでもあるんですが(後述こうじゅつするDateTimeOffsetがDateTimeのラッパーみたいなかんじになってますけれど、本質ほんしつてきにはそのぎゃくであるべきだとおもう)、そのあたり初期しょきの.NETのクラスはどうしても微妙びみょうにしょっぱいところがある)はshoganaiんで、れるんだったらKindは無視むし。これが鉄板てっぱん

DateTimeOffset

Kindを無視むしするのはいいけれど、時差じさ保存ほぞんしいよね、というとき出番でばんがDateTimeOffset。これは内部ないぶてきには ulong(DateTime) + short(オフセットぶん) の2つの保持ほじしています。まんま、DateTimeとOffset。DateTime.NowとDateTimeOffset.Nowっておなじようなかえってくるしちがいはなんなんやねん、というと、DateTimeOffsetはローカル時間じかんといったKindじゃなくて、明確めいかく内部ないぶてきに+9あいだというオフセットをっているということです。

ZeroFormatterでシリアライズするさいは、こちらはオフセットも保存ほぞんしていて、 seconds:long + nanos:int + minutes:short の14バイトの構成こうせいです。

ZeroFormatterじょうでは明確めいかくにDateTimeとDateTimeOffsetはちがうものとしてあつかってるわけですが、よくあるDateTimeをToString("o")した場合ばあいって(んで、JSONなんかにせる場合ばあいって)

// 2016-12-07T03:19:23.7683110+09:00
DateTime.Now.ToString("o");
// 2016-12-07T03:19:23.7713117+09:00
DateTimeOffset.Now.ToString("o");

と、いうふうに、完全かんぜん一緒いっしょなわけです。というか、むしろこれはDateTimeの(文字もじれつへの)シリアライズをDateTimeOffsetとして表現ひょうげんしている、ともえます。まぁ、そのほうが実用じつようじょう親切しんせつではある。が、これはDateTimeもDateTimeOffsetも区別くべつしてない(stringで表現ひょうげん)からっていうことであって、けっしてKindもシリアライズしているということではないということには注意ちゅうい。そして明確めいかくにDateTimeとしてDateTimeOffsetをちがうものとしてあつかうなら(ZeroFormatterの場合ばあい)、くもわるくもこういう表現ひょうげんはできないんだなぁ。不便ふべんだけどね。

基本きほんてきにDateTimeOffset、のほうが使つかわれるべきただしい表現ひょうげんだとおもうんですが、.NETのクラス設計せっけいじょう、DateTimeのほうが簡潔かんけつ(だし内部ないぶ構造こうぞうてきにもDateTimeOffsetはDateTime+αあるふぁというかたち)でみじか名前なまえ名前なまえちょう大事だいじ!)である以上いじょう、DateTimeの天下てんかるがないでしょう。残念ざんねんなことにDateTimeOffsetの登場とうじょうが.NET 2.0 SP1からだということもあるし。DateTimeOffsetがDateTimeで、DateTimeがLocalDateTimeだったらはなしわってくるでしょうけれど(そしてそんな構造こうぞうだったらきっとLocalDateTimeは使つかわれない)、まぁわらないものはわらないです。まぁ保存ほぞん用途ようとならUTCがいとおもうんで、現代げんだいてき意味いみではぎゃくにDateTimeOffsetの出番でばんはよりってきたともえる。データがクラウドに保存ほぞんされて世界せかい各国かっこく共有きょうゆうされるとかたりまえなので、保存ほぞんはUTC、表示ひょうじにToLocalTimeのほうが合理ごうりてき。Kindってなにやねん、とおなじぐらいOffsetってなにやねん、みたいな。

まぁLocalDateTime, ZonedDateTime, OffsetDateTimeという3しゅ表現ひょうげんというJava8方式ほうしきいですよねということになる。

NodaTime

日付ひづけ時間じかんかんしては、TimeZoneやCalendarなど、真面目まじめあつかうとより泥沼どろぬま街道かいどうぱしらなければならないわけですが、いっそ.NET標準ひょうじゅんのクラスを「使つかわない」というもあります。NodaTime代替だいたいで、Javaの実質じっしつ標準ひょうじゅんのJodaTime(のちにJava8 Date API)の移植いしょくではありますが、製作せいさくしゃがJon Skeet(Stackoverflow回答かいとうランキング世界せかいいち, Microsoft MVP, google, C# in Depth著者ちょしゃ)なので、ありがちなJava移植いしょくおえー、みたいなのではけっしてないのが一安心ひとあんしん

こういった標準ひょうじゅんクラスをえる野良のらライブラリはシリアライズ出来できないのが難点なんてんで、そうしたシリアライズの表象ひょうしょうでだけDateTime/DateTimeOffsetにえるというのはよくあるパターンですが面倒めんどうくさくはある。シリアライザの拡張かくちょうポイントを利用りようしてネイティブシリアライズ出来できるようにするのが対応たいおうかなー、というのはあります。NodaTimeは標準ひょうじゅんでJson.NETに対応たいおうした拡張かくちょうライブラリが用意よういされているというところも、(たりまえですが)わかってるなーたかくていいですね。ZeroFormatterも拡張かくちょうポイントをっているので、必要ひつようぶんだけ手書てがきして対応たいおうさせれば、まぁ、まぁ:)

まとめ

DateTimeOffsetも可愛かわいではある。とき使つかってあげてください。というわけで、つぎのアドベントカレンダーは@Marimoiroさんです!

ZeroFormatterにるC#で最速さいそくのシリアライザを作成さくせいする方法ほうほう

というタイトルで発表はっぴょうしてきました。連続れんぞくしてZeroFormatterネタなのですが、今回こんかいはC#実装じっそうのほうにフォーカスして紹介しょうかいしています。

ZeroFormatterにるC#で最速さいそくのシリアライザを作成さくせいする100おく方法ほうほう from Yoshifumi Kawai

intをシリアライズするところにフォーカスして、何故なぜ既存きそんのシリアライザはおそくて、何故なぜZeroFormatterははやいのかというところを解説かいせつしました。んでもらえれば、理屈りくつでパフォーマンスについて納得なっとくしてもらえるんじゃないかとおもいます。

以下いか会場かいじょうであったFAQなどなぞ。

エンディアンちがいは?

現在げんざいはリトルエンディアンしかサポートしてません。C#のうご環境かんきょうってほとんどリトルエンディアンなのでそこまでおおきな問題もんだいではないかな、と(Xboxはダメらしいですが)。対応たいおうしようとおもえば当然とうぜんできるんですが、Buffer.BlockCopyを多用たようしているので、そこの部分ぶぶんをバラさなきゃいけないので若干じゃっかん手間てまなのですよね(あと、性能せいのうめんでは低下ていかします)。というわけで、要望ようぼうがあってこまった、というレポートがてから対応たいおうかんがえます。一応いちおう、ビッグエンディアンでは例外れいがいくようになっていて、そこの例外れいがいメッセージのなかで、issueに自分じぶん環境かんきょういていってください、みたいなメッセージをせています。

LINQ使つかっちゃダメなの?

んなこたぁないです。場所ばしょによりけりで、ZeroFormatter内部ないぶでも、コード動的どうてき生成せいせいする部分ぶぶんかた情報じょうほうめてどうこうするところでは使つかっています。それは「アプリケーションの寿命じゅみょうなか最初さいしょいちかいだけだから」「動的どうてきつくったILをコンパイルする時間じかんのほうが比較ひかくにならないぐらいにながいので、その程度ていど節約せつやくするのは無意味むいみ」だからです。

基本きほんてきには使つかおうよ、ってのはわりはしないのですけれど、とはいえ、いままでいとされてきた領域りょういきが、かならずしもそうなの?じつはそうじゃないんじゃないの?というのをあたまれて、都度つど都度つどかんがえる必要ひつようてきているんじゃないかな、とおもってます。以前いぜんよりも。ゲームなんかではいまむかし当然とうぜんそうなのですけれど、ふつーのアプリケーションでも、いままで、単体たんたいのコンピューターでうごくものは、まぁ限界げんかいもあるし、コンピューターの性能せいのう上昇じょうしょうつづけるで、にする必要ひつようはそんななかった。サーバーアプリケーションも。でも、いま、サーバーアプリケーションってすうじゅうだいすうひゃくだいのクラスタでうごかすこともすくなくなくて、それらの場合ばあいってすこ性能せいのうげるだけで、すうひゃくだい見返みかえりがあるんですよね。ちりもればやまとなる、いままではチリはつもらなかったけれど、いまはつもりやすい環境かんきょうになってきた。ってことをかんがえると、まぁ、とくにライブラリや基盤きばん部分ぶぶんのフレームワークなんかはどこでどう使つかわれるかからないので、気合きあいれてこう!っと。

2ばんじゃダメなんですか

まぁ、ダメですね!

せっかくライブラリ公開こうかいするならおおくのひと使つかってもらいたいんですよね。これは、単純たんじゅん使つかってもらってうれしいっていうのと、おおくのひと使つかわれることによって、バグがる、機能きのうのためのアイディアがもらえる、コントリビュートしてもらえてより強力きょうりょくなライブラリになれる、などなどもあります。そういうのって、会社かいしゃにとってもメリットなんですよね。おおきめの規模きぼだったり独自どくじせいたかいライブラリは、社内しゃないだけでかかえたくないんです。まず、未来みらいがない。未来みらいがないものなんて使つかいたくない。というわけで、出来できかぎり、最初さいしょから公開こうかい意識いしきしてつくって、実際じっさい公開こうかいするわけですが、べつ公開こうかいしたからって未来みらいがあるわけでもない。おおくのひと使つかわれて、ある程度ていどメジャーかんて、はじめて未来みらいまれる。なので、やるからには精一杯せいいっぱい頑張がんばろうってかんじですね。すくなくともなんらかのインパクトはのこしたいとおもってやってます、いつも。

んで、2ばんってヒキがまったくないわけですよ。1ばんと2ばんがあったら、そりゃ1ばんえらぶでしょ。 "Second Place is the First Loser"なわけです(ちょうど勉強べんきょうかいときいたので早速さっそく使つかってみた)。というわけで、ヒキのある要素ようそ色々いろいろ必要ひつようで、いちてんが「無限むげんだい高速こうそく」で、これは勿論もちろん非常ひじょう差別さべつ要素ようそになりうる目玉めだま機能きのうです。でも、それだけだとキワモノくささがけない。やっぱパフォーマンスが最大さいだい機能きのうなんですよね、こののものは。だから、最速さいそく最初さいしょはそれを目指めざしてたわけじゃなかったんですが、スライドちゅういたように、初期しょき設計せっけい段階だんかいでStreamを排除はいじょしていたりステートをいてたりしたのがこうそうして、ある程度ていど出来でき段階だんかいで、最速さいそく現実げんじつてきねらえるとかったので、そっからさきはギアをえてガチガチにきました。そのせいで完成かんせい若干じゃっかんおくれはしたんですが、結果けっかとしては非常ひじょうかったとおもってます。

名前なまえ由来ゆらい

ゼロ速度そくどのシリアライザということで。ZeroSerializerよりZeroFormatterのほうが格好かっこういとおもいます、語感ごかんが。

Profile

Yoshifumi Kawai

Cysharp, Inc
CEO/CTO

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

X:@neuecc GitHub:neuecc

Archive