Archive - Programming

BigQueryで数列生成とC#クラスからのTable生成とデータインサート

連番を作りましょう!突然!SQL的なものを見ると、まず連番を作りたくなるのはSQLで数列を扱うからなのですが、というわけでBigQueryでも作りますし作れます。実際、Enumerable.Rangeはダイジですからね?また、地味にLINQ to BigQueryもver 0.3.3になってました。ひっそり。そんなわけで、LINQで書くと何が嬉しいのかPart2です。

LINQ to BigQuery(やBigQuery)については、最初の記事LINQ to BigQuery - C#による型付きDSLとLINQPadによるDumpと可視化をどーぞ。

0-9を作る

TempTableにInsertというわけにもいかないので、まずは愚直にUNION ALLで並べましょう。BigQueryのUNION ALLはFromをカンマで並べること(ふつーのSQLとそこが違います)で、また、Subqueryも突っ込めます。ド単純に書くとこうなる。

// 以下contextとでてきたらコレのこと
var context = new BigQueryContext(/* BigqueryService */, /* projectId */);
 
var seq = Enumerable.Range(0, 10).Select(x => context.Select(() => new { num  = x }));
context.From(seq)
    .Select(x => new { x.num })
    .Run()
    .Dump(); // DumpはLINQPadのDumpね。
 
// ↓で、こんなクエリが出てくる
/*
SELECT
  [num]
FROM
(
  SELECT
    0 AS [num]
),
(
  SELECT
    1 AS [num]
),
// 以下9まで続くので(略) */

普通に動きはしますが、馬鹿っぽいですね!少しだけカッコヨク書いてみましょうか。どうやって列を増やすか、が割と課題なのですが、BigQueryではSplitを使って増やせます。

// LINQPadでRun().Dump()って書くの面倒いのでまとめちゃう:)
public static class MyExtensions
{
    public static QueryResponse<T> DumpRun<T>(this IExecutableBigQueryable<T> source)
    {
        return source.Run().Dump();
    }
}
 
// SELECT query which references non constant fields or uses aggregation functions
// or has one or more of WHERE, OMIT IF, GROUP BY, ORDER BY clauses must have FROM clause.
context.Select(() => new { digit = BqFunc.Integer(BqFunc.Split("0123456789", ""))}).DumpRun();

怒られました!FROM句を含めないとSplitが使えないそーですなので、wordはサブクエリに分離しましょう。この辺は覚えられないので怒られたらそーいうものなんだ、って感じに対応していきましょふ。案外エラーメッセージは(親切な時は)親切です。親切じゃない時は何言ってるのか分からないエラーメッセージを吐いてきますが、まぁ7割ぐらいは分かりやすいエラーメッセージを吐いてくれます、偉い。

context
    .Select(() => new { word = "0123456789" })
    .AsSubquery()
    .Select(x => new { digit = BqFunc.Integer(BqFunc.Split(x.word, ""))})
    .DumpRun();

さすがにFROM句に並べまくるよりは、綺麗に書けてる感が出てる気がします!

0-99を作る

0-9が出来たら、あとは簡単に増やせます。ここはCROSS JOINです。0-9と0-9の直積を取ればおk。LINQでBigQueryを書くことの利点に変数にクエリを渡せて、合成可能という点が挙げられます(また、合成可能というのはLINQらしい感じさせるための重要な要素でもある)。0-9を変数に置いてやれば、コピペで同じSQLを書かないでも済みます。

var digit = context.Select(() => new { word = "0123456789" })
    .Into()
    .Select(x => new { digit = BqFunc.Integer(BqFunc.Split(x.word, ""))});
 
// これは動かないけどネ
// Cannot query the cross product of repeated fields 
digit.Into()
    .JoinCross(digit, (d1, d2) => new { d1, d2 })
    .Select(x => new { seq = x.d1.digit + x.d2.digit * 10 })
    .DumpRun();

ネ。まぁこれは動かないんですけどネ。例によってエラーメッセージが出てから対処すればいいんですが、これはSplitで生成したカラムがrepeated fieldになってるのでcross joinできないよ、とのこと。FLATTENを使えば解決します。あとOrderByを忘れてるのでOrderByも足してやりましょうか。

var digit = context.Select(() => new { word = "0123456789" })
    .Into()
    .Select(x => new { digit = BqFunc.Integer(BqFunc.Split(x.word, ""))})
    .Into()
    .Flatten(x => x.digit);
 
digit.JoinCross(digit, (d1, d2) => new { d1, d2 })
     .Select(x => new { seq = x.d1.digit + x.d2.digit * 10 })
     .OrderBy(x => x.seq)
     .DumpRun();
SELECT
  ([d1.digit] + ([d2.digit] * 10)) AS [seq]
FROM FLATTEN(
(
  SELECT
    INTEGER(SPLIT([word], '')) AS [digit]
  FROM
  (
    SELECT
      '0123456789' AS [word]
  )
), [digit]) AS [d1]
CROSS JOIN FLATTEN(
(
  SELECT
    INTEGER(SPLIT([word], '')) AS [digit]
  FROM
  (
    SELECT
      '0123456789' AS [word]
  )
), [digit]) AS [d2]
ORDER BY
  [seq]

この辺まで来ると、圧倒的に手書きよりも捗るのではないでしょうか。というか、LINQならサクサク書けますが(エラー来たら、ああはいはいIntoね、みたいに対処するだけだし)、手書きSQLはシンドイ。むしろ無理。その上で、別に意図と全然違うクエリが吐かれるわけではない、というラインはキープされてると思います。

それとネストが深くなるクエリはどう整形したらいいか悩ましいものなのですが(Stackoverflowには可読性ゼロのめちゃくちゃなインデントのBigQueryのクエリの質問が沢山転がっている!実際きちんと書くのむつかしい)、LINQ to BigQueryは、まぁまぁ読みやすい感じにきっちりフォーマットして出してくれます。若干冗長に思えるところもあるかもですが、まぁそこはルールなのだと思ってもらえれば。見やすいフォーマットといえるものにするため、微調整を繰り返したコダワリがあります。

パラメータを使う

もう一個LINQ to BigQueryのいいとこは、パラメータが使えるとこです。パラメータというか、クエリ文字列にたいして値を埋め込めるの。例えば

// こんなメソッドを作るじゃろ
Task<string[]> GetTitleBetweenRevision(int revisionIdFrom, int revisionIdTo, int limit)
{
    return context.From<wikipedia>()
        .Where(x => BqFunc.Between(x.revision_id, revisionIdFrom, revisionIdTo))
        .Select(x => x.title)
        .Limit(limit)
        .ToArrayAsync();
}
 
// こういうふうに使いますね、的な 
var rows = await GetTitleBetweenRevision(1, 200, 100);
-- 1と200が文字列置換なくSQLに埋め込まれる
SELECT
  [title]
FROM
  [publicdata:samples.wikipedia]
WHERE
  ([revision_id] BETWEEN 1 AND 200)
LIMIT 100

その場でのクエリ書きには使いませんが、プログラムに埋め込んで発行する場合なんかは当然ながらあるといいですよね、と。文字列置換や組み立てはかなり手間かかるので、ずっとぐっと遥かに楽になれるかと思います。LINQなら条件によってWhereを足したり足さなかったり、みたいな書き方も簡単です。

(この機能は0.3.1から入れました!アタリマエのように見えて、ExpressionTreeを操作する上で、地味に微妙に面倒くさいのですよー。とはいえ実用性考えるとこういうのないとアリエナイというか私が使ってて不便したんでようやっと入れました)

クエリ書きに使うのに便利といえば日付の操作は圧倒的に楽になります。例えば昨日の20時というのをBigQueryだけでやると……

context // 走査範囲を狭くするために適当に5日前ぐらいからのRangeにしてる
    .From<github_timeline>("[githubarchive:github.timeline]").WithRange(TimeSpan.FromDays(5))
    .Where(x => x.type=="CreateEvent" 
        && BqFunc.ParseUtcUsec(x.repository_created_at) >= BqFunc.ParseUtcUsec(BqFunc.StrftimeUtcUsec(BqFunc.TimestampToUsec(BqFunc.DateAdd(BqFunc.UsecToTimestamp(BqFunc.Now()), -1, IntervalUnit.Day)), "%Y-%m-%d 20:00:00"))
        && x.repository_fork == "false"
        && x.payload_ref_type == "repository")
    .Select(x => x.repository_name)
    .DumpRun();
 
// SQL
SELECT
  [repository_name]
FROM
  [githubarchive:github.timeline@-432000000-]
WHERE
  (((([type] = 'CreateEvent') AND (PARSE_UTC_USEC([repository_created_at]) >= PARSE_UTC_USEC(STRFTIME_UTC_USEC(TIMESTAMP_TO_USEC(DATE_ADD(USEC_TO_TIMESTAMP(NOW()), -1, 'DAY')), '%Y-%m-%d 20:00:00')))) AND ([repository_fork] = 'false')) AND ([payload_ref_type] = 'repository'))

結構しんどいです。厄介な日付部分を取り出すと

PARSE_UTC_USEC(STRFTIME_UTC_USEC(TIMESTAMP_TO_USEC(DATE_ADD(USEC_TO_TIMESTAMP(NOW, -1, 'DAY')), '%Y-%m-%d 20:00:00'))))

ですからね!結構かなり絶望的……。これをC#のDateTimeで操作すれば

// 今日から一日引いてその日付のみのほうを取って20時間足す
var yesterday = DateTime.UtcNow.AddDays(-1).Date.AddHours(20);
 
context
    .From<github_timeline>("[githubarchive:github.timeline]").WithRange(TimeSpan.FromDays(5))
    .Where(x => x.type=="CreateEvent" 
        && BqFunc.Timestamp(x.repository_created_at) >= yesterday // ほら超スッキリに!
        && x.repository_fork == "false"
        && x.payload_ref_type == "repository")
    .Select(x => x.repository_name)
    .DumpRun();
 
// 日付比較部分のSQLはこう出力される
TIMESTAMP([repository_created_at]) >= '2014-10-03 20:00:00.000000')

その場で書いてクエリ実行する分には、別に日付が埋め込まれようとNOW()からSQLで全部操作しようと変わらない話ですからね。楽な方でやればいいし、日付操作は圧倒的にC#で操作して持ってたほうが楽でしょう、明らかに。

Tableを作る、データを投げる

サンプルデータを扱ってるのもいいんですが、やっぱ自分でデータ入れたいですね、テーブル作りたいですね。基本的には(Google API SDKの)BigqueryServiceを使え!っていう感じなのですが、それはそれでやっぱりそれもプリミティブな感じなので、テーブル作成に関してはちょっとしたユーティリティ用意してみました。以下の様な感じで作れます。

// DataTypeUtility.ToTableFieldSchemaでTableFieldSchema[]を定義から作れる
// 匿名型を渡す以外に既存クラスだったら<T>やtypeof(T)を渡すのもOK
// もちろん手でTableFieldSchema[]を作って渡すのも構わない
new MetaTable("project_id", "mydata", "people")
    .CreateTable(service, DataTypeUtility.ToTableFieldSchema(new
    {
        firstName = default(string), // STRING REQUIRED
        lastName = default(string), // STRING REQUIRED
        age = default(int?), // INTEGER NULLABLE
        birth = default(DateTimeOffset) // TIMESTAMP REQUIRED
    }));

Web Interfaceから作ると、「空のテーブルが作れない」「スキーマはなんかカンマ定義で指定してかなきゃいけなくてダルい」という点があって存外ダルいです。bqも同様。やはり時代はLINQPad、で作る。ちなみにSTRING NULLABLEはクラス定義から抽出するのが不可能だったので(こういうところが不便なのよね……)、まあTableFieldSchema[]を作ってから schemas[1].Mode = “NULLABLE” とでも書いてください。

データの投下も同じようにMetaTableを作ってInsertAllAsyncで。

// ExponentialBackOffを渡した場合はそれにのっとってリトライをかける
await new MetaTable("project_id", "mydata", "people")
    .InsertAllAsync(service, new[]
    {
        new { firstName = "hoge", lastName = "huga", age = 20, birth = new DateTime(2010,1,1,12,13,14, DateTimeKind.Utc)},
        new { firstName = "tako", lastName = "bcbc", age = 30, birth = new DateTime(1983,3,1,10,33,24, DateTimeKind.Utc)},
        new { firstName = "oooo", lastName = "zzzz", age = 45, birth = new DateTime(2043,1,3,11,4,43, DateTimeKind.Utc)},
    }, new Google.Apis.Util.ExponentialBackOff(TimeSpan.FromMilliseconds(250), 5));

これでBigQueryのStreming Insertになります。ひどーきなので別テーブルに並走して書きたい場合は複数書いてWhenAllすれば高速で良いでしょふ。Streaming Insertはそんな頻繁、ではないですけれどそれなりに失敗することもあるので、引数にExponentialBackOff(これ自体はGoogle API SDKに含まれている)を渡せばExponential backoffでリトライを試みます。

まとめ

基本的な機能は完全に実装完了したかなあ、という感じ。0.1 ~ 0.3.3の間に自分で使っててイラッとした細かい部分をチクチク修正してきましたが、そろそろ完全に満足!といったところです。不満ない!完璧!パーフェクち!というわけで、残るはRECORD型サポートに向けて改装すれば敵なし、LINQったらサイキョーね!

な、わけですが、まぁ.NET + BigQueryというニッチに二乗かけたようなアレなので、興味関心、はあっても使ってみた!という人は少ないでしょう、というかいないでしょう、残念無念。でもBigQueryは本当に凄く良いので使ってみて欲しいんだなー。ビッグデータなんてアタクシには無縁、と思ってる人も、実は使い出、使いドコロって、絶対あります。まずはログを片っ端から突っ込んでみましょう、から始めてみませんか?

LINQ to BigQuery - C#による型付きDSLとLINQPadによるDumpと可視化

と、いうものを作りました。BigQueryはGoogleのビッグデータサービスで、最近非常に脚光を浴びていて、何度もほってんとりやTwitterに上がってきたりしてますね。詳細はGoogle BigQuery の話とかGoogleの虎の子「BigQuery」をFluentdユーザーが使わない理由がなくなった理由あたりがいいかな、超でかいデータをGoogleパワーで数千台のサーバー並べてフルスキャンするから、超速くて最強ね、という話。で、実際凄い。超凄い。しかも嬉しいのが手間いらずなところで、最初Amazon RedShiftを検討して試していたのですが、列圧縮エンコードとか考えるのすっごく大変だし、容量やパワーもインスタンスタイプと睨めっこする必要がある。それがBigQueryだと容量は格安だから大量に格納できる、チューニング設定もなし、この手軽さ!おまけにウェブインターフェイスが中々優れていてクエリが見やすい。Query Referenceもしっかり書かれてて非常に分かりやすい。もう非の打ち所なし!

触ってすぐに気に入った、んですが、C#ドライバがプリミティブすぎてデシリアライズすらしてくれないので、何か作る必要がある。せっかく作るならSQLっぽいクエリ言語なのでLINQだろう、と。それとIQueryableは幻想だと思っていたので、じゃあ代替を作るならどうするのか、を現実的に示したくて、ちょうど格好の題材が出現!ということで、LINQで書けるようなライブラリを作りました。

ダウンロードは例によってNuGetからできます。今年はそこそこ大きめのライブラリを作ってきていますが、LINQ to BigQueryは特に初回にしては大きめで割と充実、非常に気合入ってます!是非使ってみてねー。GitHubのReadMe.mdはこのブログ記事で力尽きたので適当です、あとでちゃんと書く……。

簡単なDEMO

BigQueryの良い所にサンプルデータが豊富というところがあります、というわけでGitHubのデータを扱って色々集計してみましょう。データは[publicdata:samples.github_timeline]を使ってもいいのですが、それは2011年時点のスナップショットでちょっとツマラナイ。GitHub Archiveから公開データを引っ張ってくれば、現時点での最新の、今ついさっきのリアルタイムの情報が扱えて非常に素敵(あとBigQueryはこういうpublicなDataSetが幾つかあるのが本当に最高に熱い)。ひっぱてくるやり方は書いてありますが(超簡単)、テーブル名は[githubarchive:github.timeline]です。

まずは単純なクエリということで、プログラミング言語だけでグループ化して個数を表示してみます。github.timelineは、例えばPushしたとかBranch作ったとか、雑多な情報が大量に入っているので、別にリポジトリ数のランキングではなくて、どちらかといえばアクティビティのランキング、ぐらいに捉えてもらえれば良さそうです。とりあえずトップ5で。

この例では記述と表示はLINQPadで行っています。LINQPadは非常に優れていて、C#コードが入力補完付きでサクッと書けるほか、実行結果をDumpして色々表示させることも可能です。DumpChartはLINQ to BigQueryのために独自に作ったDumpなのですが、それにより結果のグラフ化がXとYを指定するだけのたった一行

.DumpChart(x => x.repository_language, x => x.count)

だけで出来てしまう優れものです。描画は.NET標準のチャートライブラリを使っているため、棒グラフの他にも円グラフでも折れ線グラフでも、SeriesChartTypeにある35個の表示形式が選べます。見たとおり、Tooltip表示もあるので個数が大量にあっても全然確認できるといった、チャートに求められる基本的な機能は満たしているので、ちょっとしたサクッと書いて確認する用途ならば上等でしょう。

(DumpChartやQuery.GetContextのコードはこの記事の末尾にコード貼り付けてあるので、それで使ってください)

Resultsタブのほうを開けば、クエリ結果の詳細が見れます。

クエリ文字列はBigQueryの性質上、色々なところで使うはずです。そうした他所で使える可搬性のために、生成結果を人間の読める綺麗なものにする事にこだわりました(TypeScript的な)。純粋なクエリビルダとして使う(ちなみにToString()すればRunしなくてもクエリを取り出せます)ことも十分可能でしょう。Rowsに関しては切り離してグリッド表示も可能で、そうすれば簡単なソートやCSVへの書き出しといった、データベース用IDEに求められる基本的な機能も満たしています。

TotalBytesProcessedが読みづらかったのでひゅーまんりーだぶるな形に直してあるのも用意してあるところが優しさ(普通に自分が使ってて困ったので足しただけですが)。

BigQueryはウェブインターフェイスが非常に優れている、これは正直感動ポイントでした。いやぁ、RedShift、データベース管理用のIDEがろくすっぽなくて(PostgreSQL互換といいつつ違う部分で引っかかって動かないものが非常に多い)どうしたもんか、と苦労してたんですが、BigQueryはそもそも標準ウェブインターフェイスが超使いやすい。スキーマも見やすいしクエリも書きやすい。まさに神。

てわけでウェブインターフェイスには割と満足してるんですが、表示件数をドバッと表示したかったり、グラフ化もサクッとしたいし(何気にGoogle SpreadSheet連携は面倒くさい!)、日頃からデータベースもSQL Server Management StudioやHeidi SQLといったデスクトップツールを使って操作するWindows野郎としては、デスクトップで使えるIDE欲しいですね、と。それに分析やる以上、結構複雑なクエリも書くわけで、そういう時に型が欲しいなーとは思ってしまったり。LINQ to BigQueryはAlt BigQuery Query、Better BigQuery Queryとして、ただたんにC#で書けます以上のものを追求しました。そして、LINQPadとの組み合わせは、現存するBigQuery用のIDEとして最も良いはずです(そもそもBigQuery用のIDEは標準ウェブインターフェイス以外にあるのかどうか説もあるけれど)。日常使い、カジュアルな分析にも欠かせない代物となることでしょう。

Why LINQ?

LINQ to BigQueryで書く場合の良い点。一つは型が効いているので、間違っていたらコンパイルエラーで(Visual Studioで書けばリアルタイムにエラー通知で)弾かれること。別にカラム名の名前間違いなどといったことだけじゃなくて、文字列であったりタイムスタンプであったりといった型も厳密に見えているので、型の合わない関数を書いてしまうといったミスもなくせます。例えばDate and time functionsの引数が文字列なのかタイムスタンプなのかUNIX秒なのか、そして戻り値もまた文字列なのかタイムスタンプなのかUNIX秒なのか、ってのは全く覚えてられないんですが、そんな苦痛とはオサラバです。

github_timelineのカラム数はなんと200個。さすがに覚えてられませんし、それの型だってあやふやってものです(例えばboolであって欲しいフォークされたリポジトリなのかを判定するrepository_forkというカラムには”false”といったような文字列でやってくるんですぜ!?)。

全ての関数はBqFuncの下にぶら下がっていて、引数と戻り値、それにドキュメント付きです。これなら覚えてなくても大丈夫!ちなみに、ということはクエリ中の全ての関数呼び出しにBqFunc.がついてきて見た目がウザいという問題があるのですが、それはC# 6.0のusing staticを使えば解決します。

// C# 6.0 Using Static
using BigQuery.Linq.BqFunc;

楽しみに待ちましょう(C# 6.0は多分2015年には登場するんじゃないかな?)。

LINQ to BigQueryはO/Rマッパーじゃありません。いや、もちろんクエリの構築やC#オブジェクトへのマッピングは行いますが、リレーションの管理はしません。かわりに、書いたクエリがほとんどそのままの見た目のクエリ文字列になります。なので意図しない酷いクエリが発行されてるぞー、というありがちななことは起きません。そして、LINQ to BigQueryで99%のクエリが記述できます、LINQで書けないから文字列でやらなきゃー、というシチュエーションはほぼほぼ起きません。LINQとクエリ文字列を1:1に、あえてほぼ直訳調にしているのはそのためです。

また、順序を強く規制してあります、無効なクエリ順序での記述(例えばGroupBy使わずにHaving書くとかLimitの後にWhere書いてしまうとか)やSelectなしの実行はコンパイルエラーで、そもそも書けないようにしています。

左はWhereの後のメソッド、これが全部でSelectとOrderByとWhere(ANDで連結される)しか使えない。右はSelect後で、GroupBy(奇妙に思えるかもしれませんが、GroupByの中でSelectの型が使えることを考えるとこの順序が適正)やLimit、そしてRunなどの実行系のメソッドが使えるようになっています。

これらにより、LINQ to BigQueryで書いたクエリは一発で実行可能なことが期待できるものが作れます(文字列で書くと、カラムの参照周りとかで案外つまづいてエラりやすい)。さすがにExpressionの中身は検査できないんですが、概ね大丈夫で、”守られてる感”はあるかと思います。ちなみにこんな順序で書けます。

From(+TableDecorate) -> Join -> Where -| -> OrderBy(ThenBy) -> Select ->                     | -> Limit -> IgnoreCase
                                       | -> Select | -> GroupBy -> Having -> OrderBy(ThenBy) | -> IgnoreCase
                                                   | -> OrderBy(ThenBy) ->                   |

そういうの実現するためにLINQ to BigQueryはIQueryableじゃないんですが、そのことはこの長いブログ記事の後ろのほうでたっぷりポエム書いてるので読んでね!あと、こんな割とザルな構成でもしっかり機能しているように見えるのは、BigQueryのSQLがかなりシンプルなSQLだから。標準SQLにできることは、あんま出来ないんですね。で、私はそこが気に入ってます。好きです、BigQueryのSQL。別に標準SQLにがっつし寄せる必要はあんまないんじゃないかなー、SQL自体は複雑怪奇に近いですから、あんまり良くはない。とはいえ、ある程度の語彙は共用されていたほうが親しめるので、そういったバランス的にもBigQueryのSQLはいい塩梅。

最後に、Table DecoratorsTable wildcard functionsが圧倒的に記述しやすいのも利点です。

// Table Decorators - WithRange(relative or absolute), WithSnapshot 
 
// FROM [githubarchive:github.timeline@-900000-]
.From<github_timeline>().WithRange(TimeSpan.FromMinutes(15))
 
// FROM [githubarchive:github.timeline@1411398000000000]
.From<github_timeline>().WithSnapshot(DateTimeOffset.Parse("2014-09-23"))
 
// Table wildcard functions - FromDateRange, FromDateRangeStrict, FromTableQuery
 
// FROM (TABLE_DATE_RANGE([mydata], TIMESTAMP('2013-11-10'), TIMESTAMP('2013-12-01')))
.FromDateRange<mydata>("mydata", DateTimeOffset.Parse("2013-11-10"), DateTimeOffset.Parse("2013-12-1"))
 
// FROM (TABLE_QUERY([mydata], "([table_id] CONTAINS 'oo' AND (LENGTH([table_id]) >= 4))"))
.FromTableQuery<mydata>("mydata", x => x.table_id.Contains("oo") && BqFunc.Length(x.table_id) >= 4)
 
// FROM (TABLE_QUERY([mydata], "REGEXP_MATCH([table_id], r'^boo[\d]{3,5}')"))
.FromTableQuery<mydata>("mydata", x => BqFunc.RegexpMatch(x.table_id, "^boo[\\d]{3,5}"))

Table decoratorは、例えばログ系を突っ込んでる場合は障害対応や監視で、直近1時間から引き出したいとか普通にあるはずで、そういう場合に走査範囲を簡単に制御できる非常に有益な機能です。が、しかし、普通に書くとUNIXタイムスタンプで記述しろということで、ちょっとムリゲーです。それがC#のTimeSpanやDateTime、DateTimeOffsetが使えるので比較にならないほど書きやすい。

FromTableQueryも文字列指定だったりtable_idってどこから来てるんだよ!?という感じであんま書きやすくないのですが、LINQ to BigQueryでは型付けされたメタテーブル情報が渡ってくるので超書きやすい。(ところでCONTAINSだけ、BqFuncじゃなくてstring.Containsが使えます、これはCONTAINSの見た目がこれだけ関数じゃないので、ちょっと特別扱いしてあげました、他の関数は全部BqFuncのみです)

Table DecoratorsとTable wildcard functionsは非常に有益なので、テーブル名の設計にも強く影響を及ぼします。これらが有効に使える設計である必要があります。TABLE_DATE_RANGEのために(垂直分割するなら)末尾はYYYYMMDDである必要があるし、Range decoratorsを有効に使うためには極力、水平シャーディングは避けたほうが良いでしょう。そこのところを無視して、ただ単にシャーディング、シャーディングって言ってたりするのは、ちょっと、ないなー。

複雑なDEMO

ひと通り紹介は終わったので、より複雑なクエリを一つ。同じく最新のGitHubのデータを扱って、一ヶ月毎に、新しく作られたリポジトリを言語毎で集計して表示してみます。まずはグラフ化の結果から。

LINQPadではちゃんと多重グラフもメソッド一発で書けるようにしてます。コードは後で載せるとしてグラフの説明ですが、縦がパーセント、横が日付、それぞれの折れ線グラフが言語。一番上はJavaScriptで今月は43000件の新規リポジトリが立ち上がっていて全体の19%を占めてるようです。2位はJava、3位はCSS、そしてRuby、Python、PHPと続いて、この辺りまでが上位組ですね。C#はその後のC++、Cと来た次の9位で9251件・全体の4%でした。

コードは、ちょっと長いよ!

Query.GetContext()
    .From<github_timeline>()
    .Where(x => x.repository_language != null && x.repository_fork == "false")
    .Select(x => new
    {
        x.repository_url,
        x.repository_created_at,
        language = BqFunc.LastValue(x, y => y.repository_language)
            .PartitionBy(y => y.repository_url)
            .OrderBy(y => y.created_at)
            .Value
    })
    .Into()
    .Select(x => new
    {
        x.language,
        yyyymm = BqFunc.StrftimeUtcUsec(BqFunc.ParseUtcUsec(x.repository_created_at), "%Y-%m"),
        count = BqFunc.CountDistinct(x.repository_url)
    })
    .GroupBy(x => new { x.language, x.yyyymm })
    .Having(x => BqFunc.GreaterThanEqual(x.yyyymm, "2010-01"))
    .Into()
    .Select(x => new
    {
        x.language,
        x.yyyymm,
        x.count,
        ratio = BqFunc.RatioToReport(x, y => y.count)
            .PartitionBy(y => y.yyyymm)
            .OrderBy(y => y.count)
            .Value
    })
    .Into()
    .Select(x => new
    {
        x.language,
        x.count,
        x.yyyymm,
        percentage = BqFunc.Round(x.ratio * 100, 2)
    })
    .OrderBy(x => x.yyyymm)
    .ThenByDescending(x => x.percentage)
    .Run()  // ↑BigQuery
    .Dump() // ↓LINQ to Objects(and LINQPad)
    .Rows
    .GroupBy(x => x.language)
    .DumpGroupChart(x => x.yyyymm, x => x.percentage);

規模感は全体で153GBで行数が2億5千万行ぐらいだけど、この程度は10秒ちょいで返してきますね、速い速い(多分)。

メソッドチェーンがやたら続いているのですが、実際のところこれはサブクエリで入れ子になってます。随所に挟まれてるIntoメソッドで入れ子を平らにしてます。入れ子の形で書くこともできるんですが、フラットのほうが直感的で圧倒的に書きやすいく、(慣れれば)読みやすくもあります。こういう書き方が出来るのもLINQ to BigQueryの大きなメリットだとは、書いてればすぐに実感できます。

(BqFunc.GreaterThanEqualが奇妙に思えるかもしれないのですが、これは文字列だけの特例です。数値やタイムスタンプの場合は記号で書けるようにしてあるのですが、文字列はそもそもC#自体に演算子オーバーロードが定義されていないのでコンパイラに弾かれる、けどBigQuery的には書きたい時がある、というのの苦肉の策でLessThan(Equal)/GreaterThan(Equal)を用意してあります)

チャート化はGroupBy.DumpGroupChartを叩くだけなんですが、ちょっと面白いのは、ここのGroupByはLINQ to Objects(C#で結果を受け取った後にインメモリで処理)のGroupByなんですよね。

.Run()  // ↑BigQuery
.Dump() // ↓LINQ to Objects(and LINQPad)
.Rows
.GroupBy(x => x.language)
.DumpGroupChart(x => x.yyyymm, x => x.percentage);

二次元のクエリ結果を、シームレスに三次元に起こし直せるってのもLINQの面白いところだし、強いところです。モノによっては無理にSQLでこねくり回さなくてもインメモリに持ってきてから弄ればいいじゃない?という手が簡単に打てるのが嬉しい(もちろん全件持ってこれるわけがないのでBigQuery側で処理できるものは基本処理しておくのは前提として、ね)。

例えば、実のところこれの結果は、言語-日付という軸だと歯抜けがあって、全ての月に1つは言語がないと、チャートが揃いません。グラフの見た目の都合上、今回は2010-01以降にHAVINGしてありますが、その後に新しく登場した言語(例えばSwift)なんかはうまく表示できません。まぁ主要言語は大丈夫なので今回スルーしてますが、厳密にやるため、その辺の処理を、しかしSQLのままやるのは存外面倒くさい。でも、こういう処理、C#でインメモリでやる分には簡単なんですよね。なんで、一旦ローカルコンピューター側に持ってきてから、少しだけC#で処理書くか、みたいなのがカジュアルにできちゃうのもLINQ to BigQuery + LINQPadのちょっと良いところ。

さて、実際に吐かれるSQLは以下。

SELECT
  [LANGUAGE],
  [count],
  [yyyymm],
  ROUND(([ratio] * 100), 2) AS [percentage]
FROM
(
  SELECT
    [LANGUAGE],
    [yyyymm],
    [count],
    RATIO_TO_REPORT([count]) OVER (PARTITION BY [yyyymm] ORDER BY [count]) AS [ratio]
  FROM
  (
    SELECT
      [LANGUAGE],
      STRFTIME_UTC_USEC(PARSE_UTC_USEC([repository_created_at]), '%Y-%m') AS [yyyymm],
      COUNT(DISTINCT [repository_url]) AS [count]
    FROM
    (
      SELECT
        [repository_url],
        [repository_created_at],
        LAST_VALUE([repository_language]) OVER (PARTITION BY [repository_url] ORDER BY [created_at]) AS [LANGUAGE]
      FROM
        [githubarchive:github.timeline]
      WHERE
        (([repository_language] IS NOT NULL) AND ([repository_fork] = 'false'))
    )
    GROUP BY
      [LANGUAGE],
      [yyyymm]
    HAVING
      [yyyymm] >= '2010-01'
  )
)
ORDER BY
  [yyyymm], [percentage] DESC

まず、ちゃんと読めるクエリを吐いてくれるでしょ?というのと、これぐらいになってくると手書きだと結構しんどいです、少なくとも私は。ウィンドウ関数もあんま手で書きたくないし、日付の処理の連鎖は型が欲しい。それと、サブクエリ使うとプロパティを外側に伝搬していく必要がありますが、それがLINQだと入力補完が効くのでとっても楽。Into()ですぐにサブクエリ化できるので、すごくカジュアルに、とりあえず困ったらサブクエリ、とぶん投げることが可能でめちゃくちゃ捗る。大抵のことはとりあえずサブクエリにして書くと解決しますからね!処理効率とかはどうせBigQueryなので何とかしてくれるだろうから、ふつーのMySQLとかで書く時のように気遣わなくていいので、めっちゃカジュアルに使っちゃう。

ところでどうでもいい余談ですが、LAST_VALUEウィンドウ関数はリファレンスに載ってません。他にも載ってない関数は幾つかあったりして(追加された時にブログでチラッと告知はされてるようなんですけどね、リファレンスにもちゃんと書いてくださいよ……)。LINQ to BigQueryならそういうアンドキュメントな関数もちゃんと網羅したんでひじょーにお薦めです!

Generate Schema

型付けされてるのがイイのは分かったけれど、それの定義が面倒なのよねー。と、そこで耳寄りな情報。まず、全部のテーブルのちょっとした情報(table_idとかサイズとか)はGetAllTableInfoという便利メソッドで取ってこれるようにしてます(実際便利!)。で、そこから更にテーブルスキーマが取り出せるようになってます。更にそこからオマケでC#コードをstringで吐き出せるようになってます。

var context = new BigQueryContext(/* BigqueryService, projectId */);
// Get All tableinfo(table_id, creation_time, row_count, size_bytes, etc...)
var tableInfos = context.GetAllTableInfo("mydataset");
// ToString - Human readable info
tableInfos.Select(x => x.ToString()).Dump();
 
// Get TableSchema
var schema = tableInfos[0].GetTableSchema(context.BigQueryService);
 
// Build C# class definition
schema.BuildCSharpClass().Dump();

まあ、そんなに洗練されたソリューションじゃないんでアレですが、一時凌ぎには良いでしょふ。publicdataとか自分のプロジェクト下にないものは直接MetaTableクラスを作ってからスキーマ取れるようになってます。

new MetaTable("publicdata", "samples", "github_timeline")
	.GetTableSchema(Query.GetContext().BigQueryService)
	.BuildCSharpClass();
 
// =>
 
[TableName("[publicdata:samples.github_timeline]")]
public class github_timeline
{
    public string repository_url { get; set; }
    public bool? repository_has_downloads { get; set; }
    public string repository_created_at { get; set; }
    public bool? repository_has_issues { get; set; }
    // snip...(200 lines)
	public string url { get; set; }
	public string type { get; set; }
}

TableName属性がついたクラスはFrom句でテーブル名を指定しなくてもそこから読み取る、っていう風になってます(今までのコードでテーブル名を指定してなかったのはそのお陰)

リアルタイムストリーミングクエリ

Streaming Insertによりリアルタイムにログを送りつけてリアルタイムに表示することが可能に!というのがBigQuery超イカス。今までうちの会社は監視系のログはSumo Logicを使っていたのですが、もう全部BigQueryでいいね、といった状態になりました、さようなら、Sumo……。

で、リアルタイムなんですが、リアルタイム度によりけりですが、1分ぐらいの遅延やそれ以上のウィンドウを取るクエリならBigQueryで十分賄えますね。Range decoratorsが最高に使えるので、定期的にそれで叩いてやればいい。そして最近流行りのReactive ProgrammingがC#でも使えるというかむしろC#はReactive Programmingの第一人者みたいなもんなので、Reactiveに書きましょふ。Rxの説明は……しないよ?

// まぁgithub.timelineがリアルタイムじゃないからコレに関しては意味ないヨ、ただの例
 
// [githubarchive:github.timeline@1411511274158000-1411511574167000]
// [githubarchive:github.timeline@1411511574167000-1411511874174000]
// [githubarchive:github.timeline@1411511874174000-1411512174175000]
// ...
Observable.Timer(TimeSpan.Zero, TimeSpan.FromMinutes(5))
    .Timestamp()
    .Buffer(2, 1) // Buffer Window
    .SelectMany(xs =>
    {
        var context = Query.GetContext();
        context.UseQueryCache = false;
        return context.From<github_timeline>().WithRange(xs[0].Timestamp, xs[1].Timestamp)
            .Select(x => new { x.repository_name, x.created_at })
            .ToArrayAsync();
    })
    .Dump();

アプリケーション側のStreaming Insertの間隔(バッファとかもするだろうし本当のリアルタイムじゃあないでしょう?)と、そしてBigQueryのクエリ時間(数秒)の絡みがあるので、まぁ1分ぐらいからでしょうかねー、でもまぁ、多くのシチュエーションでは十分許容できるんじゃないかと思います、障害調査で今すぐログが欲しい!とかってシチュエーションであっても間に合う時間だし。

よほどの超リアルタイム(バッファもほとんど取らず数秒がマスト)でなければ、もはやAmazon Kinesisのような土管すらもイラナイ感じですね。ストレージとしてもBigQueryは激安なので、Streaming Insertが安定するならば、もうBigQuery自体を土管として使って、各アプリはBigQueryから取り出して配信、みたいな形でも良いというかむしろそれでいい。Range decoratorsが効いてるなら走査範囲も小さいんで速度も従量課金も全く問題ないしねぇ。BigQuery最強すぎる……。

データ転送

本筋じゃないのでちょっとだけ話ますが、C#ってことは基本Windows Server(AWS上に立ってる)で、データをどうやってBigQueryに送るのー?と。もちろんFluentdは動かないし、(Windowsブランチあるって?あー、うーん、そもそも動かしたい気がない)、どうしますかね、と。ストレージに突っ込んでコピーは簡単明快でいいんですが、まぁ↑に書いたようにStreamingやりたいね、というわけで、うちの会社((株)グラニ。gihyoに書いた神獄のヴァルハラゲートの裏側をCTOが語り尽くす!とか読んでくださいな)では基本的にStreaming Insertのみです。ETW/EventSource(簡単な説明はWindows high speed logging: ETW in C#/.NET using System.Diagnostics.Tracing.EventSourceを)経由でログを送って、Semantic Logging Application Block(SLAB)のOut-of-process Serviceで拾って、自家製のSink(ここは今のところ手作りする必要あり、そのうちうちの会社から公開するでしょふ)でStreaming Insert(AWS->BigQueryでHTTP経由)。という構成。

今のとこリトライは入ってますが完全インメモリなんでまるごと死んだらログはロスト。といった、Fluentdが解決している幾つかの要素は解決されてないんですが、それなりに十二分に実用には使えるところかな、と。速さとかの性能面は全く問題ありません、ETWがとにかく強いし、そっから先もasync/awaitを活かした並列インサートが使えるので他のでやるよりはずっと良いはずきっと。

TODO:

実はまだRecord型に対応してません!なのでそれに関係するFLATTENやWITHIN句も使えません!99%のクエリが再現できる、とか言っておきながら未対応……。おうふ、ま、まぁ世の中のほとんどは入れ子な型なんて使ってませんよね……?そんなことはないか、そうですね、さすがに対応は必須だと思ってるので、早めに入れたいとは思ってます。

あと、LINQPadにはDataExplorerがあって、ちゃんとスキーマ情報の表示やコネクション保持とか出来るんですねー。というわけで、真面目にそのLINQPadドライバは作りたいです、というか作ろうとしていましたし、割と作れる感触は掴んだんです、が、大きな障壁が。LINQPadドライバは署名付きであることを要求するのですが、Google APIs Client Library for .NETが、署名されてない……。署名付きDLLは全部の参照DLLが署名付きであること必要があって、肝心要のGoogleライブラリが使えないという事態に。俺々署名してもInternalVisibleToがどうのこうのとかエラーの嵐で一歩も進めないよー。Googleが署名さえしてくれてれば全部解決なのに!だいたい著名なライブラリで署名されてないのなんかGoogleぐらいだよ!もはやむしろありえないレベル!なんとかして!

IQueryable is Dead. Long live Expression!

ちょっとだけC#の話もしよふ。以下、LINQ好きだからポエム書くよ!

LINQ to BigQueryはIQueryableじゃあ、ありません。この手のクエリ系のLINQはIQueryableでQuery Providerである必要が……、あるの?IQueryableは確かにその手のインフラを提供してくれるし、確実にLINQになる。けれど、絶対条件、なの?

私がLINQ to BigQueryで絶対譲れない最優先の事項として考えたのは、LINQで書けないクエリをなくすこと。全てのクエリがLINQで書ける、絶対に文字列クエリを必要としないようにする。そのためにはIQueryableの範囲を逸脱する必要があった。そして同時に強く制約したかった、順序も規定したいし、不要なクエリは(NotSupported!)そもそも書けないようにしたかった。これらはIQueryableに従っていては絶対に実現できないことだった。

LINQがLINQであるためにはクエリ構文はいらない。Query Providerもいらない。LINQ to XMLがLINQなのは何故?Parallel LINQがLINQであるのは何故?Reactive ExtensionsがLINQであるのは何故?linq.jsがLINQであるのは何故?そこにあるのは……、空気と文化。

LINQと名乗ること自体はマーケティングのようなもので、形はない。使う人が納得さえすれば、LINQでしょう。そこにルールを求めたがる人がいても、ないものはないのだから規定しようがないよ?LINQらしく感じさせる要素をある程度満たしてればいい。FuncもしくはExpressionを使ってWhereでフィルタしSelectで射影する(そうすればクエリ構文もある程度は使えるしね)。OrderBy系の構文はOrderBy/OrderByDescending/ThenBy/ThenByDescendingで適用される。基本的な戻り値がシーケンスっぽい何かである。うん、だんだん満たせてくる。別に100%満たさなくても、70%ぐらい満たせばLINQらしいんだよ。SelectManyがなくたって、いい。どうせNotSupportedExceptionが投げられるのなら、最初からないのと何が違うというの?

LINQ to BigQueryからはLINQらしさを感じられると思っています。最優先事項の全てのBigQueryのクエリを書けるようにすることやNotSupportedを投げないことなどを持ちつつも、可能な限りLINQらしさを感じさせるよう細心の注意を払ってデザインしました。極論言えば私がLINQだって言ってるんだからLINQなのですが(何か文句ある?)、多くの人には十分納得してもらえると考えています。LimitをTakeで”書けない”とかね、BigQueryらしくすることも使いやすさだし、LINQらしくすることも使いやすさ。この辺は私の匙加減。

と、いうわけでIQueryableは、データベース系クエリの抽象化というのが幻想で、無用の長物と化してしまったのだけど、しかし役に立たなかったかといえば、そうじゃあない。LINQだと感じさせるための文化を作る一翼をIQueryableは担っていたから。データベース系へのクエリはこのように定義されていると”らしい”感じになる。その意識の統一にはIQueryableは必要だった、間違いなく。しかし時は流れて、もう登場から6年も経ってる。もう、同時にかかった呪いからは解放されていいんじゃないかな?みんなでIQueryableを埋葬しよう。

と、いうのがIQueryableを使ってない理由。死にました。殺しました。IQueryableは死んだのですが、しかしExpressionは生きています!LINQ to BigQueryも当然Expressionで構成されています。空前のExpression Tree再評価の機運が!で、まぁしかしだからってふつーのアプリのクエリをExpression Treeでやりたいかは別の話ね。やっぱ構築コストとか、そもそもBigQueryは比較的シンプルなSQLだから表現しきれたけどふつーのSQLは複雑怪奇で表現できないだろー、とか、色々ありますからね。まぁ、あんま好ましく思ってないのは変わりません。

コストの話は、BigQueryの場合は完全に無視できるのよね。クエリのレスポンスが普通のDBだったら数msだけど、BigQueryは数千~数万msと桁が4つも5つも違う。リクエスト数もふつーのクエリは大量だけどBigQueryはほとんどない(一般ユーザーが叩くものじゃないからね)。なので、ほんとうの意味でExpression Treeの構築や解釈のコストは無視できちゃう。そういう、相当富豪的にやっても何の問題もないというコンテキストに立っています。だからLINQ to BigQueryはあらゆる点で完全無欠に有益。

LINQPad用お土産一式

Query.GetContextとかDumpChartとかは、LINQPadの左下のMy Extensionsのとこに以下のコードをコピペってください。それで有効になります。本当はLINQPad Driver作ってそれ入れれば有効になるようにしたかったんですが、とりあえず今のところはこんなんで勘弁してくだしあ。こんなんでも、十分使えますので。

// Import this namespaces
BigQuery.Linq
System.Windows.Forms.DataVisualization.Charting
Google.Apis.Auth.OAuth2
Google.Apis.Bigquery.v2
Google.Apis.Util.Store
Google.Apis.Services
 
public static class Query
{
    public static BigQueryContext GetContext()
    {
        BigQueryContext context;
        // Replace this JSON. OAuth2 JSON Generate from GCP Management Page. 
        var json = @"{""installed"":{""auth_uri"":""https://accounts.google.com/o/oauth2/auth"",""client_secret"":"""",""token_uri"":""https://accounts.google.com/o/oauth2/token"",""client_email"":"""",""redirect_uris"":[""urn:ietf:wg:oauth:2.0:oob"",""oob""],""client_x509_cert_url"":"""",""client_id"":"""",""auth_provider_x509_cert_url"":""https://www.googleapis.com/oauth2/v1/certs""}}";
 
        using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(json)))
        {
            // Open Browser, Accept Auth
            var userCredential = GoogleWebAuthorizationBroker.AuthorizeAsync(ms,
                new[] { BigqueryService.Scope.Bigquery },
                "user",
                CancellationToken.None, new FileDataStore(@"LINQ-to-BigQuery")) // localcache
                .Result;
 
            var bigquery = new BigqueryService(new BaseClientService.Initializer
            {
                ApplicationName = "LINQ to BigQuery",
                HttpClientInitializer = userCredential
            });
 
            context = new BigQueryContext(bigquery, "write your project id");
        }
        // Timeout or other options
        context.TimeoutMs = (long)TimeSpan.FromMinutes(1).TotalMilliseconds;
        return context;
    }
}
 
public static class MyExtensions
{
    public static IEnumerable<T> DumpChart<T>(this IEnumerable<T> source, Func<T, object> xSelector, Func<T, object> ySelector, SeriesChartType chartType = SeriesChartType.Column, bool isShowXLabel = false)
    {
        var chart = new Chart();
        chart.ChartAreas.Add(new ChartArea());
        var series = new Series { ChartType = chartType };
        foreach (var item in source)
        {
            var x = xSelector(item);
            var y = ySelector(item);
            var index = series.Points.AddXY(x, y);
            series.Points[index].ToolTip = item.ToString();
            if (isShowXLabel) series.Points[index].Label = x.ToString();
        }
        chart.Series.Add(series);
        chart.Dump("Chart");
        return source;
    }
 
    public static IEnumerable<IGrouping<TKey, T>> DumpGroupChart<TKey, T>(this IEnumerable<IGrouping<TKey, T>> source, Func<T, object> xSelector, Func<T, object> ySelector, SeriesChartType chartType = SeriesChartType.Line)
    {
        var chart = new Chart();
        chart.ChartAreas.Add(new ChartArea());
        foreach (var g in source)
        {
            var series = new Series { ChartType = chartType };
            foreach (var item in g)
            {
                var x = xSelector(item);
                var y = ySelector(item);
                var index = series.Points.AddXY(x, y);
                series.Points[index].ToolTip = item.ToString();
            }
            chart.Series.Add(series);
        }
        chart.Dump("Chart");
        return source;
    }
}

GCPの管理ページからOAuth2認証用のJSONをベタ貼りするのとプロジェクトIDだけ書いてもらえれば使えるかと。最初にブラウザ立ち上がって認証されます、2回目以降はローカルフォルダにキャッシュされてるので不要。まぁ色々ザルなんですが、軽く使う分にはいいかな、と。

まとめ

いやもう本当に、この手のソリューションではBigQueryが群を抜いて凄い。Azure使ってる人もAWS使ってる人(実際、うちのプロダクトはAWS上で動かしてますがデータはBigQueryに投げてます)もオンプレミスの人もBigQuery使うべきだし、他のものを使う意味が分からないレベル。とにかく試せ、であり、そして試すのは皆Googleアカウントは絶対持ってるはずだからワンポチするだけで立ち上がってるし、最初から膨大なサンプルデータがあるので簡単に遊べるし、一発で気にいるはず、間違いない。

そしてWindows(C#)の人には、LINQ to BigQuery + LINQPadがベストなツールとなってくれるはず。むしろあらゆるBigQueryを扱う環境の中でC#こそが最高といえるものになってくれるよう、色々やっていきたいですね。

ジェネリッククラス内の静的フィールドの挙動について

今メインで作ってるほげもげの進捗があんま良くないので、たまには少し小ネタでも。ジェネリッククラス内(静的クラスでも普通のクラスでもどっちでもいいです)の静的フィールドは、それぞれ独立して、各型に唯一のフィールドとして存在できます。違う型では共有されず、同じ型ないでは共有される、という挙動です。あまり良い例でもないですが、例えばこんな感じ。

public static class InstanceGenerator<T>
{
    static readonly Func<T> generator;
 
    static InstanceGenerator()
    {
        var newExpr = Expression.Lambda<Func<T>>(
            Expression.New(typeof(T).GetConstructor(Type.EmptyTypes)));
        generator = newExpr.Compile();
    }
 
    public static T CreateNew()
    {
        return generator.Invoke();
    }
}
 
class Program
{
    static void Main(string[] args)
    {
        var p1 = InstanceGenerator<Program>.CreateNew();
        var p2 = InstanceGenerator<Program>.CreateNew();
        var s = InstanceGenerator<StringBuilder>.CreateNew();
    }
}

さすがにこれだとnew Program()って書けよって話なので、クソの役にもたたなすぎる例なんです が、いちおう、Compileという重たい処理をキャッシュできますね、みたいな感じ。(このクラスはクソの役にも立たないけど)(こういうジェネリッククラスの挙動は)便利便利。で、それはいいんですけど、もしフィールドがジェネリックじゃない場合はどーなるでしょう?こんな風に、非許可の型チェックを入れてみたりします。

// 静的クラスでもふつーのクラスでもどっちでもいーですよ
public class InstanceGenerator<T>
{
    static readonly Func<T> generator;
    static readonly HashSet<Type> disallowType = new HashSet<Type>
    {
        typeof(StringBuilder),
        typeof(ArrayList)
    };
 
    static InstanceGenerator()
    {
        var newExpr = Expression.Lambda<Func<T>>(Expression.New(typeof(T).GetConstructor(Type.EmptyTypes)));
        generator = newExpr.Compile();
    }
 
    public InstanceGenerator()
    {
        if (disallowType.Contains(typeof(T))) throw new Exception("その型は許可されてません!");
    }
 
    public T CreateNew()
    {
        return generator.Invoke();
    }
}
 
class Program
{
    static void Main(string[] args)
    {
        // ok
        var pg = new InstanceGenerator<Program>().CreateNew();
 
        // exception
        var sb = new InstanceGenerator<StringBuilder>().CreateNew();
 
    }
}

HashSet<Type>は特に<T>とは関係のないフィールド。かつ「意図としては」readonlyで全ジェネリッククラスで共有して欲しい。で、実際どーなってるかというと……確認しませう。

public class InstanceGenerator<T>
{
    static readonly Func<T> generator;
 
    // 呼ばれたのをチェックするために遅延実行のシーケンスをかませる
    static readonly HashSet<Type> disallowType = new HashSet<Type>
        (Enumerable.Range(1, 3).Select(x => { Console.WriteLine(x); return typeof(int); }))
    {
        typeof(StringBuilder),
        typeof(ArrayList)
    };
 
    // 以下同じなので略
}
 
class Program
{
    static void Main(string[] args)
    {
        // 1,2,3,1,2,3と出力されて、(当然)二回HashSetが初期化されてるのが分かる
        var pg = new InstanceGenerator<Program>().CreateNew();
        var sb = new InstanceGenerator<StringBuilder>().CreateNew();
 
    }
}

といった感じに、非ジェネリックフィールドも当たり前のように共有されることなく、各ジェネリッククラスで独立して存在します。当たり前っちゃあ当たり前です(readonlyじゃないstatic fieldだって存在できるし、readonlyだってimmutableとは限らないので、そんな利用者都合の区別をコンパイラがつけられはしない!)

けれど多くの静的フィールドを使うシチュエーションにとっては、あんま都合よくないかな、と。大したことナイといえばないですが、正規表現のCompileしたのとか別個で持ってたくないし、その他色々色々。気になるっちゃあ気になります。さて、どうすればいいか、っていうと

internal class InstanceGenerator
{
    protected static readonly HashSet<Type> disallowType = new HashSet<Type>
    {
        typeof(StringBuilder),
        typeof(ArrayList)
    };
}
 
// 静的クラスじゃなければ継承するとか
public class InstanceGenerator<T> : InstanceGenerator
{
    // 以下略
}
 
// 静的クラスの時は適当に誤魔化すしかない
internal static class _InstanceGenerator
{
    public static readonly HashSet<Type> disallowType = new HashSet<Type>
    {
        typeof(StringBuilder),
        typeof(ArrayList)
    };
}
 
public static class InstanceGenerator<T>
{
    // 中略
 
    // なんかひどぅぃ
    public static T CreateNew()
    {
        if (_InstanceGenerator.disallowType.Contains(typeof(T))) throw new Exception("その型は許可されてません!");
        return generator.Invoke();
    }
}

独立した外の型として定義せざるをえないので、適当に誤魔化すしかないですね!あとはふつーにゆーてぃりてぃクラスとして独立させるとか設計で回避、的なアレ。

ちなみに

例がクソややこしく感じた人には一番シンプルなものを。

public static class MyClass<T>
{
    public static object X = new object();
}
 
class Program
{
    static void Main(string[] args)
    {
        var b = Object.ReferenceEquals(MyClass<int>.X, MyClass<string>.X);
        Console.WriteLine(b); // false
 
        MyClass<int>.X = 1000; // 違うもクソも外からセットできるし
        Console.WriteLine(MyClass<int>.X); // 1000
        Console.WriteLine(MyClass<string>.X); // System.Object
    }
}

ようはこれだけじゃないですかーやだー無駄にこねくりまわした例は余計わかりづらいー。

仕様

言語仕様的には「4.4.2 オープン型とクローズ型」の最後の部分の話です。

すべての型は、”オープン型” か “クローズ型” のいずれかに分類されます。オープン型は、型パラメーターと一緒に使用する型です。より具体的には、次のとおりです。

  • 型パラメーターはオープン型を定義します。
  • 配列型は、要素の型がオープン型の場合のみ、オープン型です。
  • 構築された型は、1 つ以上の型引数がオープン型の場合のみ、オープン型です。構築された入れ子になった型は、1 つ以上の型引数または外側の型の型引数がオープン型の場合のみ、オープン型です。

クローズ型とは、オープン型でない型です。

実行時、ジェネリック型宣言内のすべてのコードは、ジェネリック宣言に型引数を適用することによって作成されたクローズ構築型のコンテキストで実行されます。ジェネリック型内の各型パラメーターは、特定の実行時の型にバインドされます。すべてのステートメントおよび式の実行時の処理ではクローズ型が発生し、オープン型は、コンパイル時の処理でのみ発生します。

クローズ構築型には独自の静的変数セットがあり、このセットは他のクローズ構築型と共有されません。オープン型は実行時には存在しないため、オープン型に関連付けられた静的変数はありません。2 つのクローズ構築型は、同じ非バインド ジェネリック型から構築された場合は同じ型になり、対応する型引数も同じ型になります。

あとは「10.5.1 静的フィールドとインスタンスフィールド」でも触れられています。

静的フィールドは特定のインスタンスの一部ではなく、クローズ型 (4.4.2 を参照) のすべてのインスタンス間で共有されます。クローズ クラス型のインスタンスがいくつ作成される場合でも、関連付けられたアプリケーション ドメインに対する静的フィールドのコピーは 1 つだけです。

この辺りのは言い方がややこしいんで言語仕様とにらめっこしてるだけだとあんま頭に入ってこない系ですにぇ。

Immutable CollectionsとSubject(Rx)の高速化について

最近はUniRxというUnity向けのReactive Extensionsの実装を書いているので、そこにImmutableなCollectionのちょーどよく分かりやすい使い道の実例があるので紹介しようかと思います。Rx自体はImmutable Collections使ってるわけではありませんが、同様の(簡易的)実装を内部で持っています。UniRxも同様に簡易実装を中で持つ形です。

Immutable Collectionsを知らにゃい?詳しくはNET Framework Blog - Immutable collections ready for prime timeを。または、以前に私がセッションで発表した資料もありますので、それも見てください。neue cc - .NETのコレクション概要とImmutable Collectionsについて。1.0リリースからもベータ版のリリースは続いていて、今回はそのベータのほうを使います(ダウンロードはNuGetでプリリリースのものを有効にするだけです)。何故かと言うと、今回使うImmutableArrayはベータのほうにしか入っていないからです。

素朴なSubject

最も素朴なSubjectを作ってみましょう。SubjectはEventのRx的な表現で+=とInvokeが出来るもの、とでも思ってもらえれば。

public class MySubject<T> : IObservable<T>, IObserver<T>
{
    List<IObserver<T>> observers = new List<IObserver<T>>();
 
    // Subscribeするとリストに貯めて
    public IDisposable Subscribe(IObserver<T> observer)
    {
        observers.Add(observer);
        return null; // 本来は戻り値をDisposeするとRemoveだけど省略
    }
 
    // OnNextで配信
    public void OnNext(T value)
    {
        foreach (var item in observers)
        {
            item.OnNext(value);
        }
    }
 
    // OnErrorとOnCompletedは中略
}

こんなもんですね、簡単簡単。実際使う場合は

// とりあえずこういうの用意しとかないとメンドーなので。
// Rxを参照してるならSubescribe(x => { })でいいよ!
public class ActionObserver<T> : IObserver<T>
{
    readonly Action<T> onNext;
 
    public ActionObserver(Action<T> onNext)
    {
        this.onNext = onNext;
    }
 
    public void OnNext(T value)
    {
        onNext(value);
    }
 
    // OnErrorとOnCompletedは中略
}
 
// で、こんなかんぢ
var subject = new MySubject<int>();
 
subject.Subscribe(new ActionObserver<int>(x => Console.WriteLine(x)));
subject.Subscribe(new ActionObserver<int>(x => Console.WriteLine(x * 2)));
 
subject.OnNext(500); // 500, 1000

概ね見たまんまな単純な話ですねー、さて、この実装は素朴すぎるので簡単に死にます。マルチスレッドで、とかそういうことじゃなく、例えば……

// 呼ばれるとイベント登録しに走るような場合
subject.Subscribe(new ActionObserver<int>(x => subject.Subscribe(new ActionObserver<int>(_ => Console.WriteLine(x)))));
 
//ハンドルされていない例外: System.InvalidOperationException: コレクションが変更されました。列挙操作は実行されない可能性があります。
subject.OnNext(10000);

foreachの最中にList本体にAddやRemoveといった操作は許可されていないのですねー。そんなのしねーよ、と突っぱねることはRxの使い方の場合は実際できないので、対処が必要です。一番簡単なのはまるっとコピーすること。

// MySubject<T>.OnNext
public void OnNext(T value)
{
    foreach (var item in observers.ToArray()) // 列挙はコピー
    {
        item.OnNext(value);
    }
}

こういう対処はLINQ to XMLのドキュメント宣言型コードと命令型コードの混在のバグ (LINQ to XML)でも薦められている、特別でもない一般的なテクニックということで、場合によっては普通に使っても構わない話だと思います。スレッドセーフにするのもlock仕込むだけ。

public IDisposable Subscribe(IObserver<T> observer)
{
    lock (observers)
    {
        observers.Add(observer);
    }
    return null;
}
 
public void OnNext(T value)
{
    IObserver<T>[] array;
    lock (observers)
    {
        array = observers.ToArray();
    }
    foreach (var item in array)
    {
        item.OnNext(value);
    }
}

高速化する

素朴な実装の問題は、まぁパフォーマンス。コピーだから一概に悪いとは言わなくて、場合によっては全然普通に使って構わないというのは頭に入れて欲しいのですけれど、さすがにOnNextのような、イベントが叩かれるような、頻度の高いもので毎回コピーが走るのは些か厳しい。じゃあどうしよう?そうだConcurrent Collectionだ!ふむ……。でもConcurrentQueueとかだと(今回省いてますが)Removeするのがむつかしい。ConcurrentDictionaryで代替だ!でも列挙の具合が不透明(並列コレクションの列挙の挙動は結構色々なのでそれなりに注意が必要です)、パフォーマンス的にもただのforeachよりは劣るよねえ、せっかくやるならエクストリームな性能を追い求めたい気もする。

と、そこで出てくるのが(?)Immutable Collections、の、ImmutableArray。

注意しなきゃいけないのは別にImmutable Collections使ったからって必ずしも早いとかってわけじゃないです。むしろ多くの場合でImmutable Collectionsは不適でしょう。コレクションには特性があって、それにうまく合致しなければむしろ遅いです。今回のシチュエーションではImmutableArrayが割と最適にハマります(同じAPIを持ったリスト的なものにImmutableListがありますが、今回だとArrayのほうが良い)。とりあえず見てみましょう、か。

class MySubject<T> : IObservable<T>, IObserver<T>
{
    ImmutableArray<IObserver<T>> observers = ImmutableArray.Create<IObserver<T>>();
 
    public IDisposable Subscribe(IObserver<T> observer)
    {
        // スレッドセーフな入れ替え
        while (true)
        {
            var oldCollection = observers;
            var newCollection = oldCollection.Add(observer);
            var comparedCollection = ImmutableInterlocked.InterlockedCompareExchange(ref observers, newCollection, oldCollection);
 
            if (comparedCollection == oldCollection) return null; // 変更対象がAddしている間に変わってなければ成功
        };
    }
 
    public void OnNext(T value)
    {
        // 普通にぐるぐる回しても安全
        foreach (var item in observers)
        {
            item.OnNext(value);
        }
    }
 
    // OnErrorとOnCompletedは中略
}

Listの宣言をImmutableArrayに変えて、あとは、フィールドの代入が全然変わってる!そう、ImmutableArrayの差し替えはちょっと面倒くさいのです。所謂CAS(Compare And Swap)という奴で、「大抵の場合は衝突しないけど原理的にたまに衝突する」という場合のために、グルグル回って比較して置き換えるという手段を取ります。ImmutableCollectionsにはそういった処理のためのヘルパーメソッドがImmutableInterlockedクラスに幾つか用意されています。ここではImmutableArrayで使えるImmutableInterlocked.InterlockedCompareExchangeを使いました。

特性

ImmutableArrayの中身は、配列です。Addは中で内部の配列をまるっとコピーして、新しい配列を作っています。foreachはその内部の配列に対して列挙かけるだけなので、普通の配列を回すのと性能はまるっきり変わらない。なので、Addのコストは非常に高いけれど、他は通常の配列と変わらないぐらい高速というのが特性です。

なんでSubjectの実装にImmutableArrayが適切かというと、「追加や削除よりも圧倒的に多く列挙が呼ばれる」からですね。そもそも普通にOnNext書けば毎回コピーが走るので、だったら追加の時のコピー一発で済ませられるなら遥かに高効率と思われるのではないでしょーか。

これにより、イベント的な使用でのSubjectのパフォーマンスは、ノーロック・ノーコピーで、配列とほぼ同等の性能が出ます。完璧!

まとめ

Immutable Collectionsは、まぁ、実際のとこガチッと使えるシーンがはまるケースはぶっちけあんまないと思います!コレクションとしての重要度は 普通のジェネリックコレクション>コンカレントコレクション>超えられない壁>イミュータブルコレクション でしょうし、使うコレクションを探す場合も、そこから順番で考えたほうが良いでしょふ。neue cc - .NETのコレクション概要とImmutable Collectionsについてでも書いたのですけれど、別にイミューラブルコレクション=速い、というのは大間違いです。むしろかなりピーキーで、性能特性をしっかり考えないと全く使いこなせません。

それでも今回のように使えるかもしれない!?ような局面というもの自体は存在するので、覚えておいて損はないと思います。次の.NET Frameworkに標準で入るのかどうかは今のところ分かりませんが、多分入るんじゃないかなー、Roslynで使いたいようだしー、って感じなので先取りしちゃりましょう!

A Beginners Guide to Reactive Extensions with UniRx

どうも始めましての人は始めまして、@neueccといいます。この記事はUnity アセット真夏のアドベントカレンダー 2014 Summer!というイベントの23日目です。クリスマスのアレ!真夏に……!しかしクリスマスのアレは比較的脱落も少なくのないのですが、これは見事ーに続いてます。しかも日付が変わった瞬間に公開されることの多いこと多いこと。〆切というのは23:59:59だと思っている私には辛い話です……。さて、前日はnaichiさんの【うに部屋】Unityのゲーム投稿サイトにアセット検索機能を付けてみたでした。便利でいいですねー、UniRxも使ったアセットとして沢山並ぶ日が来ると、いいなぁ。

Reactive Programming

とは。と、ここで7/30に行われた【第1回】UnityアセットまみれのLT大会で使ったスライドが!

Reactive Programming by UniRx for Asynchronous & Event Processing from Yoshifumi Kawai

LTということで制限時間5分だったんですが当然終わるわけなくて凄まじく早口でまくしたてて強引に終わらせたせいで、全くワカラン!という感想を頂きましたありがとうございますごめんなさい。簡単にかいつまみますと、

Reactive Programmingはガートナーのハイプサイクル(記事では2013ですがこないだ出た2014年版のApplication DevelopmentでもOn the Riseに入っています)やThought Works Technology Raderといった有名な技術指標にもラインナップされるほど、注目を浴びている技術です。Scala周辺からもThe Reactive Manifestoといった文章が出ていますし、JavaでReactive Programmingを実現するRxJavaはGitHubのStarが2995、Objective-C用のReactiveCocoaは5341、JavaScriptでもRxJSが1792、bacon.jsが2864と、知名度・注目度、使われている度は非常に大きくなっています。

Reactive Programming自体は別に近年始まったわけでもなく、昔からたまに盛り上がっては消え、って感じなので、「へぇ~この技術2年前くらいに流行ってたよね 2年前くらい前に見たわ」って思う人もいるかもですが、大事なのは、ちゃんと実用に乗った、ということです。実験的なライブラリの段階はとうに超えて、RxJavaやReactiveCocoaの知名度が示す通り、完全に実用レベルに乗りました。

UniRxはReactive Extensions(Rx)というMicrosoftの開発した.NET用のライブラリ(現在はOSS化)を私がUnity用に移植したものです。現在のReactive(Rx)Hogeの源流は、この.NETのRxにあります。ほとんどのライブラリから言及され、原理原則や用語はRx.NETに従っていることが多い。というわけでRx.NETは革命的に素晴らしいわけなのです、が、しかし、Unityでは動きません。それはRx.NETが本来のC#の機能を全面的に使いすぎてUnityのC#では動かせないから……。少なくともiOSのAOT問題を全く突破できない……。

が、どうしてもUnityで使いたいので移植(といってもソースコードレベルではほとんど自前で書いてるのでインターフェイスと挙動を合わせているという感じで割と書き下ろしです、一部は純粋に移植してますが)+UnityはUnityで.NETとは異なるところもあるので、Unityで使って自然になるような改良を施したのがUniRxになります。

ムリョーですよ、ムリョー。FREE!。私はLINQやRxの大ファンなんで、とにかく使ってもらいたい欲求のほうが強くて。そして勿論、自分で使えない状態にも耐えられなくて!気になったらGitHubでStarつけてください(Star乞食)、勿論AssetStoreのほうでもいいですよ:)

UniRxは非同期やイベント処理をReactive Programmingの概念を元に大きく簡単にします。Unityにも非同期ライブラリ、イベントライブラリは沢山あります。それらと比べたUniRxの強みは「Rxであるということ」です。Reactive Programmingは現状かなりブームになっているとおりに、その手法の正しさ、威力に関しては実証済みです。また、手法やメソッド名などが同一であるということは、既に普及しているRx系のライブラリのドキュメントがまんま使えます。そしてUniRxで学んだやり方は他のプラットフォームに移っても同じように使えるでしょう。ネイティブAndroid(RxJava)でもネイティブiOS(ReactiveCocoa)でも.NET(Rx.NET)でもJavaScript(RxJS)でも、そういった先々への応用性もまた、選ぶべき理由になると思います。

Introduction

前置きが長い!さて、この記事を出すちょっと前に【翻訳】あなたが求めていたリアクティブプログラミング入門という素晴らしい記事が翻訳されました!はてブでも500以上集まってましたし、実際めっちゃ良い記事です。この記事はRxJSを用いて具体的な説明を行っていますが、勿論UniRxでも同様のことができます(というわけで、結論んとしては↑の記事読んでもらえればいいんでさー、とか投げてみたい)。ちょっとやってみましょう。最初の例はダブルクリックの検出です。

using System;
using UniRx;
using UnityEngine;
 
public class Intro : MonoBehaviour
{
    void Start()
    {
        // 左クリックのストリーム
        var clickStream = Observable.EveryUpdate()
            .Where(_ => Input.GetMouseButtonDown(0));
 
        // Buffer:250ミリ秒以内に連続してクリックされたものをまとめる
        clickStream.Buffer(clickStream.Throttle(TimeSpan.FromMilliseconds(250)))
            .Select(xs => xs.Count) // 250ミリ秒以内検出したクリック数
            .Where(x => x >= 2) // 2個以上のみにフィルタ
            .Subscribe(_ => Debug.Log("ダブルクリックされた!")); // foreachみたいな
    }
}

このスクリプト(Intro.cs)をMainCameraでもなんでも適当な何かに貼り付けてもらえれば、画面のどこでもダブルクリックされればログに流れます。

この例は6行しかない単純なものですが、多くの要素が詰め込まれています!そして、実際、詰め込まれすぎていてかなり難しい!わからん!というわけで真面目に分解していきます。

Rx is LINQ

Rxについてまとめると「時間軸に乗るストリームに対するLINQ」です。時魔法です。先の記事でも「FRPは非同期データストリームを用いるプログラミングである」といってました。(ドヤァするわけですが、私は遥か昔、2011年の時点で同じこと言ってましたからね!)。このことさえピンと来れば割としっくり来るんですが、同時にこれがしっくり来るというところまでが敷居となる。ところが、RxはこのことをLINQとして表現することによりグッと敷居を下げました。なんだ、LINQと一緒じゃん!って。え、LINQがワカラナイ?それは、普通にC#の必須技術なので是非学んでください!(色々種類ありますがLINQ to Objectsだけでいいです。以前に@ITでLINQの仕組み&遅延評価の正しい基礎知識を書いたりAn Internal of LINQ to Objectsというスライドで発表したりしてるんで読んでください)

簡単に同じように見れることを説明すると、LINQ to Objectsでは

new[] { 1, 2, 3, 4, 5 }
    .Where(x => x % 2 == 0)
    .Select(x => x * x);

のように、配列をフィルタリングして別の形に射影できます。

配列、int[]の横軸は当然ながら長さです。Rxは時間を横軸に取ることができます。どういうことか、というと、例えば何かをタップするというイベントは図にしたらこういう表現ができます。

ということは、同様にWhereしたりSelectしたりできる。

少しピンと来ました?さて、配列はnew[]{}やGetComponentsなどで手に入れることができますが、Rxで扱うためのイベントストリーム(IObservable<T>、ちなみにLINQ to Objectsは配列をIEnumerable<T>として扱う)はどこに転がっているのか。UniRxにおいて一番お手軽なのはObservable.EveryUpdateです。ゲームループは、ループというぐらいに1フレーム毎にUpdateが毎回呼ばれるサイクルなわけですが(参考:Script Lifecycle Flowchart)、つまり60fpsなら1/60秒毎に発生する時間軸にのったイベントとみなせられます。一度、時間軸上に乗るイベントとみなせられれば、それは全て無条件にRxで取り扱えます。

実際のところ、イベントに限らずあらゆるものがRxに見せかけることができます。非同期だってx秒後に一度だけ発生するイベントと考えれば?配列は0秒で沢山の値が発行されるイベントと考えれば?Unityのコルーチン(IEnumerator)だって乗せられる。

全てのものをRx化(IObservable化)すれば、あとは好きなようにLINQのメソッドで合成してしまえる。あらゆる素材(イベント・非同期・配列・コルーチン)を鍋(IObservable)に突っ込んで、料理(Where, Select, etc…)して食べる(Subscribe)。というのがRxの基本の基です。料理方法は色々あるので、そこが次のキモですね。

Composable

というわけでclickStreamがマウスの左クリックのストリームになったということは分かったでしょうか!?

var clickStream = Observable.EveryUpdate()
    .Where(_ => Input.GetMouseButtonDown(0));

左クリックがあったフレームだけにフィルタリングしているということですねー。では次は?

clickStream.Buffer(clickStream.Throttle(TimeSpan.FromMilliseconds(250)))

Rxの利点として、イベントが変数として扱えることです。どういうことか、戻り値にすることもできるし、フィールドやプロパティとして公開することもできるし、自分自身と結合することもできる。というわけで、clickStreamが二個出てるのは、自分との合成なんですね(わっかりづらい!)

そこまではいいとしてThrottleが分かりづらい!Throttleは一定時間毎(この場合は250ミリ秒)に値が観測できなかったら値を流す、という挙動です。

スロットル、流れてくる値を絞り込んでいるんですね。Rxは時間軸上に乗っているので、こうした時間関係を扱うメソッドが豊富です。Sample(一定時間毎のもののみを流す)、Delay(一定時間後に流す)、Timeout(一定時間たっても値がなければエラーにする)、Buffer(一定時間の間値を溜めてから流す)などなど。一見分かりづらいですが、そもそも普通にも書きづらい、そういったものがメソッド一発で書けるというのもRxの魅力です。

じゃあどういうことかというと、もうまだるっこしいので全部のせますが

ThrottleしたものをBufferしているのは、値が流れてくるまで値を溜める(そして配列にして後続に流す)、ということです。あとはSelectで配列の個数、つまり250ミリ秒の間にクリックされた回数に変形して、Whereでフィルタリング(ダブルクリック、つまり2個以上ならばOK)。なるほどねー?

ちなみにコレは(250ミリ秒以内に)クリックされ続けてるとずっと実行されません。最後にクリックされてから250ミリ秒後に、その間に2回以上クリックされてると実行される。が正しい表現でしょうか。この動作が望ましい場合もあれば望ましくない場合もある、ダブルクリックと一口でいっても定義は案外複雑なので、その辺はチューンしてみてください。その辺もclickStream.Timestamp()や.TimeInterval()でその時刻や前との差分なんかが簡単に取れます。

Subscribeって?

Subscribeはforeachです。配列をforeachで消費するように、RxではSubscribeで消費する。イベントストリームなのだからeventのSubscribe(購読)。foreachしなければ配列は動かないように、RxもSubscribeしなければ始まりません!(というのは正確には語弊があり、ObservableにはHotとColdという性質があり、HotはSubscribeしなくても動いている、とかかんとか、とかがありますが今は無視します)。RxにおけるSubscribeは、値・エラー・完了を一手に受け取ります。

Observable.EveryUpdate() // 0, 1, 2, 3, 4, 5, 6, 7, 8,....
    .Take(5)
    // .Do(x => { if(x == 3) throw new Exception(); } )
    .Subscribe(
        x => Debug.Log(x + "Frame"), // OnNext
        ex => Debug.Log("Exception!" + ex), // OnError
        () => Debug.Log("Complete!")); // OnCompleted

通常、イベントに終わりはありませんが、Rxのイベントストリームは終わりを持たせることができます。Takeは値をx個取得したら強制的に終点ということにするもの。長さ5のイベントストリーム。というわけで↑のコードはOnNextが5回呼ばれた後にOnCompletedが呼ばれます。もしDo(値が通った時にメソッドを実行する)のコメントアウトを外すと、OnNextが3回呼ばれた後にOnErrorが呼ばれます。この場合はOnCompletedは呼ばれません。なお、OnNext/OnError/OnCompletedはどれも書いても書かなくてもいいです(その場合はOnErrorはグローバルに例外をそのままthrow、OnNextとOnCompletedは何もしない、ということになる)

Rxのイベントストリームは以下の原則に必ず従います。これは他のRx系列のReactive Programmingライブラリも同じものを採用しています。

OnNext* (OnError | Oncompleted)?

OnNextが0回以上呼ばれた後に、OnErrorもしくはOnCompletedが1回または0回よばれます。

購読ということは解除(Unsubscribe)はあるのか、というと、あります!Subscribeの戻り値は必ずIDisposeableで、それをDisposeすることが解除になります。

var subscription = Observable.Interval(TimeSpan.FromSeconds(1))
    .Subscribe(_ => Debug.Log("hogehoge!"));
 
Observable.EveryUpdate()
    .Where(_ => Input.GetMouseButtonDown(0))
    .Take(1)
    .Subscribe(_ => subscription.Dispose());

Intervalはx秒毎に値を発行するというもの、↑の例では1秒おきにhogehoge!と表示されます。もし左クリックがあったら、その戻り値をDisposeしているので、これで値の発行は止まります。Take(1)なので、左クリックを監視するストリームもTake(1)が終わったら自動的にEveryUpdateの監視を解除しています。

var subscription = Observable.Interval(TimeSpan.FromSeconds(1))
    .Subscribe(_ => Debug.Log("hogehoge!"));
 
Observable.EveryUpdate()
    .Where(_ => Input.GetMouseButtonDown(0))
    .Take(1)
    .Subscribe(_ => subscription.Dispose());

この形式の何がいいか、というと、入れ物に入れてまとめて購読解除できます。

CompositeDisposable eventResources = new CompositeDisposable();
 
void Start()
{
    Observable.Interval(TimeSpan.FromSeconds(1))
        .Subscribe(_ => Debug.Log("hogehoge!"))
        .AddTo(eventResources);
 
    Observable.EveryUpdate()
        .Where(_ => Input.GetMouseButtonDown(0))
        .Subscribe(_ => Debug.Log("click!"))
        .AddTo(eventResources);
}
 
void OnDestroy()
{
    eventResources.Dispose();
}

CompositeDisposableはIList[IDisposable]みたいなもので、IDisposableをまとめて突っ込めます。AddToはメソッドチェーンのまま突っ込めるようにするUniRxの定義している拡張メソッド。こうしてためておいて、Destroyでまとめて解除、ができます。こうした変数で扱えるという性質により、イベントの管理がかなり容易になっています。勿論、文字列で止めて、などもない完全なタイプセーフですしね。

余談:片方のストリームが発動したら止める、という処理は割と定形なので明示的にsubscriptionをDisposeするようなコードを書かなくても、TakeUntilが使える。

Observable.Interval(TimeSpan.FromSeconds(1))
    .TakeUntil(Observable.EveryUpdate().Where(_ => Input.GetMouseButtonDown(0)))
    .Subscribe(_ => Debug.Log("hogehoge!"));

色々あるってことです!メソッド一覧を眺めて使い道を考えよう!

非同期について

Unityにある素敵な素敵なコルーチンはあまり素敵ではない。WWWでyieldできて非同期扱えてサイコー、ではない。なんで?C#のyieldが非同期を扱うためのものじゃないから。try-catchできないからyield return StartCoroutineしたら例外はあの世に飛んでいく。それを避けるためにWWWのようにwww.textとwww.errorを持つようにあらゆる非同期はどうでもいいコンテナを持たなければならなくて?そしてそもそもIEnumeratorは戻り値を持てない。だから戻り値を持たせたかったらコールバックの形に返るしかない。酷い、酷い、醜悪な話だ。

// こんなこるーちんを用意して
IEnumerator GetGoogle(Action<string> onCompleted, Action<Exception> onError)
{
    var www = new WWW("http://google.com/");
    yield return www;
 
    if (!www.error) onError(new Exception(www.error));
    else onCompleted(www.text);
}
 
// なんかダラダラしてる
IEnumerator OnMouseDown()
{
    string result;
    Exception error;
    yield return StartCoroutine(GetGoogle(x => result = x, x => error = x));
    if(error != null) { /* なんかする */ }
 
    string result2;
    Exception error2;
    yield return StartCoroutine(GetGoogle(x => result2 = x, x => error2 = x));
    if(error2 != null) { /* なんかする */ }
}

基本的に破綻している。別に分離しないで単純に単純なWWWを延々と連鎖している限りは、少しはまともに綺麗になるかもしれないけれど、それは処理を分離できないという問題を産む。一つの巨大な無駄にデカいCoroutineと重複コードを避けられない。何れにせよコルーチンは非同期を扱うためのベストソリューションでは全くない(ところでNode.jsはyieldで立派に上手く非同期を扱っているじゃないか!と思う方もいるかもしれませんが、アレはyieldが戻り値を返しているから可能であって、どちらかといえばC# 5.0のasync/awaitに近い。C#のyield returnとは少し別物)

Rxならどう書くか?全てをObservableの連鎖フローに変換する。

// xが完了したらそれでy、完了したらzのダウンロードの連鎖のフローをLINQクエリ式で
var query = from x in ObservableWWW.Get("http://google.co.jp/")
            from y in ObservableWWW.Get(x)
            from z in ObservableWWW.Get(y)
            select new { x, y, z };
 
// Subscribe = "最後に全部まとまったあとの"コールバック(ネストしないから処理が楽)
query.Subscribe(x => Debug.Log(x), ex => Debug.LogException(ex));

メソッドチェーン(もしくはクエリ式)で、コールバックのネスト数を最小に抑えてフロー化、一体となって処理する。

また、並列ダウンロードなども簡単に行えるのは大きな利点かもしれない。

var parallel = Observable.WhenAll(
    ObservableWWW.Get("http://google.com/"),
    ObservableWWW.Get("http://bing.com/"),
    ObservableWWW.Get("http://unity3d.com/"));
 
var cancel = parallel.Subscribe(xs =>
{
    Debug.Log(xs[0]); // google
    Debug.Log(xs[1]); // bing
    Debug.Log(xs[2]); // unity
});
 
// これでキャンセルできる
cancel.Dispose();

こんな風に並べて簡単に並列処理できます。また、全てのRxのSubscribeは戻り値をDisposeすることでキャンセルできる。文字列でStopCoroutineする時代はさようなら。

コルーチンについて

と、コルーチンを腐しましたが、しかし実際コルーチンは素晴らしいツールだと思っています。フレームワークネイティブの機構であり、それはやはり強いのです。無理にRxで全部書くことはまったくもって完全に”可能”なのですが、それよりは素直にコルーチンで書いて、Rxはそれをまとめることに徹してみればいい。コルーチンもイベントと同じく一つの素材。と考えればイイ。

public IObservable<string> AsyncA(string msg)
{
    return Observable.FromCoroutine(observer => AsyncA(msg, observer));
}
 
// 戻り値のあるコルーチン(IObserverがコールバックを扱うコンテナとして考える、値通知とエラー通知、両方を内包する)
private IEnumerator AsyncACore(string msg, IObserver<string> observer)
{
    Debug.Log("a start");
    yield return new WaitForSeconds(1);
    observer.OnNext(msg); // 値を通知
    observer.OnCompleted();
    Debug.Log("a end");
}
 
public IObservable<string> AsyncB()
{
    return Observable.FromCoroutine(AsyncBCore);
}
 
// 戻り値のないコルーチン
private IEnumerator AsyncBCore()
{
    Debug.Log("b start");
    yield return new WaitForEndOfFrame();
    Debug.Log("b end");
}
 
// こんな使い方
var cancel = AsyncA()
    .SelectMany(_ => AsyncB())
    .Subscribe();
 
// 例によってコルーチンを止めたければ戻り値をDisposeする
cancel.Dispose();

Observable.FromCoroutineによって変換できて、あとは好きなように合成できる。FromCoroutineは戻り値があってもなくてもOK。作る時にお薦めなのはIEnumeratorはprivateにして、変換後のIObservableのほうだけをpublicにすること。

uGUI

iOS用のReactiveCocoaが非常に受け入れられているように、リアクティブプログラミングはGUIとの相性が非常に良いです。そこでUniRx ver.4.5では既にUnityの新GUIシステムに対応!.AsObservable()と書くだけで変換できます。例えば

GetComponentInChildren<Button>().AsObservable()
    .Subscribe(x => Debug.Log("クリックされた!"));

のように書けます。詳しい話はいつか!

そのうち書く何か(予告!)

「Pull vs Push」RxはPush型。Pull型のアーキテクチャはFindGameObjectsで探してきて何かする、もしくはなんとかManagerでオブジェクトを維持して、何れにせよそれらで蓄えて配列上のものに対して処理をグルッと書く。RxはPush型、何かして欲しい何かを各GameObject自身が通知する。ようするにイベント。イベントと違うのはRxならばイベントの集合体を圧倒的にコントロールしやすいこと(沢山のイベントストリームを処理するためのメソッドがある!)。本来、必要な時に必要な情報だけを送ってくるPush型のほうが、必要かどうかを取得してから考える必要のあるPull型よりもパフォーマンスも良くなる可能性が高いし。しかし生のイベントはコントロールが難しすぎて中々使えなかった。それをRxが解き放つ。とかうんたらかんたら。

学習リソース

UniRxの特徴として、Reactive Extensions系の学習リソースを流用できることが上げられますが、実際RxJavaのWikiは非常にお薦めです。沢山あるメソッドの説明が図入りで説明されていて非常に分かりやすい、例えばFiltering Observablesとか。

また、Introduction to Rxは非常に充実したチュートリアルを提供し、RxMarbles: Interactive diagrams of Rx Observables ではインタラクティブにメソッドの挙動を確認できます。

こうしたリソースにひたすらタダ乗り出来るのが強い!

更新履歴。

さて、いまさらですが、UniRxの最初の発表日は2014/04/19に開催されたすまべん特別編「Xamarin 2.0であそぼう!」@関東での発表でした。

UniRx - Reactive Extensions for Unity from Yoshifumi Kawai

この時にGitHubにリポジトリを公開して、アセットストアに審査出し。そこから審査に3回ほどこけて、一月後に2014年05月28日に公開されました、わーぱちぱち。ってしかしブログに解説書く書く詐欺で解説を書かないでいました、ほげえ。だからこの記事が最初の解説記事なんですねー、えー……。なんとなく書かないでいたのは、次のバージョンではもっと良くなってるから!を延々と繰り返してたから説。特に大きく変わったのがver.4.3(ちなみにUniRxのバージョンが4始まりなのは、審査にこける度に間違ってメジャーバージョンを上げちゃってたからです、気付いた時には戻せず……)

ver 4.3 - 2014/7/2
 
Fix iOS AOT Safe totally
MainThreadSchedule's schedule(dueTime) acquired time accuracy
MainThreadDispatcher avoid deadlock at recursive call
Add Observable.Buffer(count, skip)
Change OfType, Cast definition
Change IScheduler definition
Add AotSafe Utilities(AsSafeEnumerable, WrapValueToClass)
Change Unit, TimeInterval and Timestamped to class(for iOS AOT)
Add Examples/Sample7_OrchestratIEnumerator.cs

Unity + iOSのAOTでの例外の発生パターンと対処法という記事を書いて、そこそこ反響あったのですが、その成果を突っ込んでます。というわけで、このバージョンでiOSのAOT問題を大きく解決しました。そしてver4.4。

ver 4.4 - 2014/7/30
 
Add : Observable.FromEvent
Add : Observable.Merge Overload(params IObservable[TSource][] / IEnumerable[IObserable[TSource]])
Add : Observable.Buffer Overload(timeSpan, timeShift)
Add : IDisposable.AddTo
Add : ObservableLogger(UniRx.Diagnostics)
Add : Observable.StartAsCoroutine
Add : MainThreadDispatcher.RegisterUnhandledExceptionCallback
Add : Examples/Sample08, Sample09, Sample10, Sample11
Performance Improvment : Subject[T], OnNext avoids copy and lock
Performance Improvment : MainThreadDispatcher, avoids copy on every update
Change : Observable.ToCoroutine -> ToAwaitableEnumerator
Fix : ObservableMonoBehaviour's OnTriggerStay2D doesn't pass Collider2D

機能的にはObservableLoggerを入れたのとIDisposable.AddToでリソース管理のガイドを示したのが大きいんですが、一番大きいのはパフォーマンス改善かなあ、と。ガチで使うと大量に出てくるSubject[T]や、絶対経由することになるMainThreadScheduler/Disptacherの性能を限界まで向上させたので、あまりネックになることはないのではかな、と。で、公開まだなver4.5。

ver 4.5 - 2014/8/19(アセットストアへは審査中)
 
Add : ObservableWWW Overload(byte[] postData)
Add : Observable.Buffer Overload(windowBoundaries)
Add : LazyTask - yieldable value container like Task
Add : Observable.StartWith
Add : Observable.Distinct
Add : Observable.DelaySubscription
Add : UnityEvent.AsObservable - only for Unity 4.6 uGUI
Add : UniRx.UI.ObserveEveryValueChanged(Extension Method)
Add : RefCountDisposable
Add : Scheduler.MainThreadIgnoreTimeScale - difference with MainThreadScheduler, not follow Unity Timescale
Add : Scheduler.DefaultSchedulers - can configure default scheduler for any operation
Fix : DistinctUntilChanged iOS AOT issue.
Fix : Remove IObservable/IObserver/ISubject's covariance/contravariance(Unity is not support)
Fix : UnityDebugSink throws exception when called from other thread
Fix : Remove compiler error for Windows Phone 8/Windows Store App
Breaking Change : MainThreadSchduler follow Unity Timescale
Breaking Change : All Timebased operator's default scheduler changed to MainThreadScheduler
Breaking Change : Remove TypedMonoBehaviour.OnGUI for performance improvment
Performance Improvment : AsyncSubject[T]
Performance Improvment : CurrentThreadScheduler
Performance Improvment : MainThreadScheduler

本当はこの記事と同時にアセットストアでも公開!と行きたかったんですが審査がまだー……。uGUI対応のイベントハンドリングを足したり、UIで使うの見越したUniRx.UI.ObserveEveryValueChangedの追加とか、uGUIへの対応を見越した基礎部分を足してっていってる感じですね。こうしたUIでの利用法はuGUIのノウハウと共に貯めていきたい/公開していきたいと思っています。

あと凄く大きいのが時間ベースのメソッドで使われるデフォルトのスケジューラをScheduler.MainThreadに変えたことで、ふつーに使う分には全てがUnityのTimescaleの影響下にあるシングルスレッドで動く状態になるので、違和感というかハマりどころ(ObserverOnMainThreadしなかったから死んだ!オマジナイにObserveOnMainThreadって書きまくったせいで性能が!)を消せたのかなー、と思います。ここは本家Rxとは当然デフォルトが違うことになりますが、Unityネイティブに寄せるべきだろう、という判断です。

あと地味にWindows Phone 8やWindows Store Appにも対応しました。いや、最初のバージョンでは対応してたんですが機能足してるうちに、どうやらコンパイル通らなくなってしまっていて……。Platform切り替えないと気づけないのが辛いですねえ、Unity Cloud BuildのWindows Phone対応はよ!いちおう「We’ll be adding more platforms as the service matures.」ってあるので、適当に待ちましょう。そもそもBetaの現状は重すぎてそういう次元ですらないですしね。

次回更新では、現状ExecuteInEditModeでは動かないので、それに対応したものを出す予定です。

最後に

と、いうわけでどうでしょう?使ってみたくなってもらえれば幸いです。怒涛の更新のとおりにやる気はかなりあります、というか私はグラニという会社のCTOをしているんですが(CM放送などをした「神獄のヴァルハラゲート」が代表作です)、開発中の次のプロダクトに投下していて、実プロダクトで使う気満々というかドッグフーディングというか、ともあれ現状「枯れてない」というのは否定出来ないのですが、基本的なバグは既に概ね殺せているのではかなぁ、と、そこは信頼してくれると嬉しいですね。今回はUniRxの初めての記事だったので基礎の基礎的な話になりましたが(そうか?)、今後は応用的な記事などもどんどん出していきます。

Reactive Extensions自体は、私は2011年には@ITに連載:Reactive Extensions(Rx)入門という記事を書いていたり、そもそも2009年に最初のベータ版が出た時から追っかけて記事を書き続けていたりとneue.cc/category/programming/rx、5年間延々とRxを触っているので、さすがにかなり詳しいのではないかと自負するところです(ちなみに最初の記事は.NET Reactive Framework メソッド探訪第一回:FromEventでした。そう、当初はReactive Frameworkって名前だったんですね、更にもっと源流はMicrosoft Live Labs Voltaになります)。

繰り返しますが日本ではReactive Programmingのブームは定期的に起こっては消え(最初のほうでバズったのは2010年のやさしいFunctional reactive programming(概要編)でしょうか)、って感じですが、Microsoftは理論やプロトタイプに留まらず延々と改良を続け、完全に実用ベースに載せ、本物のブームを作り上げたことには本当に感嘆します。勿論、ブーム自体はMicrosoftよりは、そこから波及していったRxJavaやReactiveCocoaの貢献が非常に大きいです。私もUniRxで、Reactive Programingの強力さをUnityでも示し、大きなブームが巻き起こせればなあ、なんて野望は抱いていますね!

UniRxへの質問があれば、GitHubのIssuesUnityのForumに立ててあるスレッドでもぜひぜひですが、そこでは英語でお願いしたいのでちょっと敷居がー、ということであれば、普通にTwitterの@neuecc宛てに気楽に言ってください。またはTwitterで「UniRx」で常時検索してますんで独り言みたいな感じでポストしてもらえればチェックします。

あと会社単位でも、会社間交流ということで共催の社内勉強会などできれば嬉しいかなー、と思ってますので、是非グラニと勉強会やりたいという方いらっしゃいましたらお声がけください。今までもKLabさん - 2014年3月度ALMレポート(株式会社グラニ様との合同開催)やドリコムさん等と行ってきています。グラニのUnity以外の技術、というか現状はそちらがメインなのですが、というのはgihyo.jpのグラニがC#にこだわる理由という記事を見て頂ければなのですが、サーバーサイドをPHPやPython、RubyじゃなくてC#(Windows Server + ASP.NET)でやる、というのが強みです。勿論、今後クライアントサイドもC#(Unity)でやっていきます。

さて、明日は野生の男さんの「無料アセットで簡単IBL!」です。楽しみ楽しみー、ではでは!

Amazon Kinesis + Reactive Extensionsによる簡易CEP

AWSのAmazon Kinesis!大規模なストリーミングデータをリアルタイムで処理する完全マネージド型サービス。うーん、いかにもわくわくしそうなキーワードが並んでいます。そしてついに先日、東京リージョンでも利用可能になったということでAWS Summitの最中もwktkして、どうやって利用したもんかと考えてました。だって、リアルタイムにイベントデータが流れてくる→オブザーバブルシーケンス→Reactive Extensions(Rx)、という連想になるのは自然なことですよね?

Kinesisとは

Rx、の前にKinesisとは。【AWS発表】 Amazon Kinesis – ストリームデータのリアルタイム処理を見れば事足りますが、表現するなら土管、ですかね。イベントデータの。以下ぽんち絵

Streamの中はShardという単位で分かれていて、データを放り込む時はPartitionKeyを元に、どのShardに突っ込まれるか決まる。読み書き性能自体は完全にShardの数で決まっていて、1シャード毎にWriteは1MB/sec - 1000Req/sec, Readは2MB/sec - 5Req/secとなってます。事前に負荷状況を予測していくのと、随時、Split(Shardの分割)とMerge(Shardの統合)してスケーリングしていく、って感じですかねえ。API自体は単純で、あんま数もないので簡単に理解できるかと。

APIが単純なのはやれることが少ないから。土管。情報を左から右に流すだけのパイプ。その代わり入力は限りなく無限にスケールしていく(Shardを増やしまくれば)。では出力は?というと、Kinesis Applicationとよばれる、といっても実体は、別にAPIをほぼほぼポーリングで叩いてデータ取り出して何か処理するものをそう呼んでるだけ。で、取り出すのはAPI叩いて保存されたデータを読むだけ。

そう、ポーリング。Kinesis自体は一時保管所であって、本当のリアルタイムでPubSub配信するわけじゃあない(用途としては問題ないレベルで低遅延にはなるけれど)。保存時間は24時間で、その間はStream中のどこから(最初からでも最新からでも任意の位置から)でも取り出すことができる。一時保管所がわりにS3を使ったりすると、ゴミは貯まるしどこまで取ったかとか煩わしくなるけれど、Kinesisの場合はStreamの形状になっているのでとてもやりやすい。ただしKinesisは制限として1レコード辺り50KBまで。更にHTTPで投げる際にBase64になってブヨっと膨らむ。

ObservableKinesisClient

C#でKinesisを使うには、AWS SDK for .NETを使えばAmazonKinesisClient入ってます。ソースコードも公開されてるしNuGetでも入れられるし、APIはとりあえずAsyncに対応してるし、APIデザインもちょっと奇妙なところもあるけれど、一応全て統一されたモデルでデザインされてるので、割と結構良いと思ってます。

Kinesis、データの登録はPutRecordでバイナリ投げるだけなので単純なのですが、取り出しの方はいささか面倒で、DescribeStreamによるStream内のShard情報の取得、GetShardIteratorによるShardIterator(どの位置から取得開始するか、の情報)の取得、それを元にGetRecord、そして延々とポーリングのためのループ。と、繰り返す必要があります。

というわけかで、まずは利用例の方を。

// とりあえずAWSのキーと、ストリーム名で生成する感じ
var client = new ObservableKinesisClient("awsAccessId", "awsSecretAccessKey", RegionEndpoint.APNortheast1, streamName: "KinesisTest");
 
// データの登録。オブジェクトを投げ込むとJSONシリアライズしたのを叩き込む。
await client.PutRecordAsync(new { Date = DateTime.Now, Value = "ほげほげほげほげ" });
 
// ObserveRecordDynamicでJSONのストリームとして購読できる
client.ObserveRecordDynamic()
    .Where(x => x.Value != "ほげ") // xはdynamicなのでどんなSchemaのJSONも自由に辿れる
    .Select(x => x.Date + ":" + x.Value)
    .Subscribe(Console.WriteLine);

はい。ObserveRecordDynamicで、リアルタイムに流れてくるデータを簡単に購読できます。IObservableなので、Rxによって自由にクエリを書くことが可能。また、何のデータが流れてくるか分からないストリームのために、JSONはdynamicの形でデシリアライズされています。(IntelliSenseの補助は効きませんが)スキーマレスに、あらゆるデータをRxで処理できます。もちろん、型付けされたものが欲しければObserverRecord<T>を、今は実装してないですが、まあ簡単につくれます:)

以下ObservableKinesisClient本体。

// JSON.NET, AWSSDK, Rx-Mainの参照が必要
public class ObservableKinesisClient
{
    readonly UTF8Encoding encoding = new UTF8Encoding(false);
    readonly JsonSerializer serializer = new JsonSerializer() { Formatting = Newtonsoft.Json.Formatting.None }; // ThreadSafeだよ
    readonly string streamName;
    readonly AmazonKinesisClient kinesis; // ThreadSafeなのかは知らない(ぉぃ
 
    // コンストラクタはもっとまぢめにやりましょう
    public ObservableKinesisClient(string awsAccessId, string awsSecretAccessKey, RegionEndpoint endPoint, string streamName)
    {
        this.kinesis = new AmazonKinesisClient(awsAccessId, awsSecretAccessKey, endPoint);
        this.streamName = streamName;
    }
 
    // ようするにObjectを1レコードずつJSONで突っ込むもの
    public async Task<PutRecordResponse> PutRecordAsync(object value)
    {
        using (var ms = new MemoryStream())
        using (var sw = new StreamWriter(ms, encoding))
        using (var jw = new JsonTextWriter(sw) { Formatting = Formatting.None })
        {
            serializer.Serialize(jw, value);
            jw.Flush();
            ms.Position = 0;
 
            var request = new PutRecordRequest
            {
                StreamName = streamName,
                Data = ms,
                PartitionKey = Guid.NewGuid().ToString() // PartitionKeyは適当にランダム
            };
 
            // つまり1レコード1HTTP POSTということになる。
            // 大量に投げる際は素朴すぎてアレゲ感があるので、実際にやるときはまとめてから放り込んで
            // 取り出す側も↑の構造を前提にして取り出すよーな感じにしたほうがいーかもデスネー
            return await kinesis.PutRecordAsync(request).ConfigureAwait(false);
        }
    }
 
    // Dynamicが嫌な場合はSerialize<T>でおk。とりあえずこの例ではdynamicでやります。
    // Client内部で分配しちゃったほうがきっと自然にやさしい(Publish().RefCount())
    public IObservable<dynamic> ObserveRecordDynamic()
    {
        return Observable.Create<dynamic>(async (observer, cancellationToken) =>
        {
            var isRunningNextPipeline = false;
            try
            {
                // まずShard一覧を取得する
                // TODO:これを使いまわしちゃうとShardsの増減には対応してないよ!
                // 毎回DescribeStream読むのもアレだしたまに問い合わせとかがいいの?
                var describeStreamResponse = await kinesis.DescribeStreamAsync(new DescribeStreamRequest { StreamName = streamName }).ConfigureAwait(false);
                var shards = describeStreamResponse.StreamDescription.Shards;
 
                var nextIterators = new List<string>();
                foreach (var shard in shards)
                {
                    if (cancellationToken.IsCancellationRequested) return; // CancellationTokenの監視だいぢだいぢ
 
                    // ShardIteratorTypeは実際は取り出した位置を記録しておいてAFTER_SEQUENCE_NUMBERでやるか、LATESTでやるかがいーんじゃないでしょーか?
                    var shardIterator = await kinesis.GetShardIteratorAsync(new GetShardIteratorRequest
                    {
                        StreamName = streamName,
                        ShardId = shard.ShardId,
                        ShardIteratorType = ShardIteratorType.TRIM_HORIZON, // TRIM_HORIZON = 最初から, LATEST = 最新, AT_SEQUENCE_NUMBER = そこから, AFTER_SEQUENCE_NUMBER = 次から
                    }).ConfigureAwait(false);
 
                    var record = await kinesis.GetRecordsAsync(new GetRecordsRequest { ShardIterator = shardIterator.ShardIterator }).ConfigureAwait(false);
 
                    // Shardの順番で回してるので、このPushの順番は必ずしも「時系列ではない」ことにチューイ!
                    foreach (var item in record.Records)
                    {
                        PushRecord(item, observer, ref isRunningNextPipeline); // ObserverでPush!Push!Push!
                    }
 
                    nextIterators.Add(record.NextShardIterator);
                }
 
                // NextShardIteratorがある状態で無限ぐるぐる
                do
                {
                    if (cancellationToken.IsCancellationRequested) return; // ところどころCancellationTokenの監視 Part2
 
                    for (int i = 0; i < nextIterators.Count; i++)
                    {
                        if (cancellationToken.IsCancellationRequested) return; // ところどころCancellationTokenの監視 Part3
 
                        var shardIterator = nextIterators[i];
 
                        var record = await kinesis.GetRecordsAsync(new GetRecordsRequest { ShardIterator = shardIterator }).ConfigureAwait(false);
 
                        // こちらでも、やはりShardの順番で回してるので、状況によって必ずしも時系列にはならないことにチューイ!
                        foreach (var item in record.Records)
                        {
                            PushRecord(item, observer, ref isRunningNextPipeline); // ObserverでPush!Push!Push!
                        }
 
                        nextIterators[i] = record.NextShardIterator;
                    }
 
                    await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false); // 実質ポーリングなのでなんとなくDelayをちょっと入れてみる
 
                    nextIterators = nextIterators.Where(x => x != null).ToList(); // 明らかに非効率なこの実装はテキトーなんで真面目にやるなら真面目に書いてください:)
                } while (nextIterators.Any());
            }
            catch (Exception ex)
            {
                if (isRunningNextPipeline)
                {
                    throw;
                }
                else
                {
                    observer.OnError(ex);
                }
 
                return;
            }
 
            observer.OnCompleted();
        });
    }
 
    void PushRecord(Record record, IObserver<dynamic> observer, ref bool isRunningNextPipeline)
    {
        using (var sr = new StreamReader(record.Data, encoding)) // item.DataにMemoryStreamの形で1レコードが受け取れる
        using (var jr = new JsonTextReader(sr))
        {
            var obj = serializer.Deserialize(jr);
            isRunningNextPipeline = true;
            observer.OnNext(obj); // 1レコードをPush
            isRunningNextPipeline = false;
        }
    }
}

PutRecordAsyncはまんま、JSONにシリアライズしたデータを投げ込んでるだけです。ObserverRecordDynamicのほうはちょっと複雑っぽいですが、やってることは順に、DescribeStreamAsyncでShard一覧を取得→それぞれのShardでGetShardIteratorAsyncで始点の取得・GetRecordsAsyncで最初のデータを取得しobserverに配信→取得できたNextShardIteratorを元にデータ取得と配信の無限ループ。です。

コメントで色々書いてありますが、Shard単位で処理していくのでレコードのSequenceNumberの順にPushされているわけではないことと、ShardがSplitやMergeで変動することへの対応は必要よね、とか考えることは色々ありますね。あと、Readの制限が5Req/secとかなり少ないので、複数処理する必要があるなら、できればリクエストは分配してやりたいところ。RxならPublishで分配、ついでにRefCountでSubscriberが0になったら購読解除というのが自然に書けるので、その辺も入れてやるといいかなー、なんて思います。とはいえ、基本的にはデータ取ってOnNextで垂れ流すという、それだけに収まってはいます(ほんとだよ!)。

従来はこの手のコードはyield returnで処理するはずですが、それがOnNextに変わっているという事実が面白い!勿論、同期API + yield returnにすることも可能ですが、AWS SDKの同期APIは非同期のものを.Resultで取ってるだけで非同期のほうがネイティブになるので、同期API使うのはお薦めしません。非同期時代のLINQ、非同期時代のイテレータ。中々面白くありません?UniRx - Reactive Extensions for UnityのFromCoroutineでも、IObserverをyielderとして渡して、非同期のイテレータを作れる(コンバートできる)ようにしています。こういうのも一つのデザイン。

like CEP(with LINQPad)

CEP(Complex Event Processing)は最近良く聞くようになりましたねー、MicrosoftにもStreamInsightというかなり立派なプロダクトがあるのですが、あんまり話を聞かないし将来性もビミョーそうなので見なかったことにしましょう。ちなみにStreamInsightは2.1からRxと統合されたりして、この手のイベントストリームとRxとが相性良いこと自体は証明済みです。

そんなわけでMicrosoft周辺では全然聞きませんが、日本だとLINEでのEsper CEPの活用例とかNorikra:Schema-less Stream Processing with SQLで盛んに聞いて、まーたMicrosoft周辺によくある、一歩先を行ったと思ったら周回遅れ現象か!とか思ったり思わなかったり。

というわけで、Norikraの紹介スライドのクエリ5つをRxで書いてみましょう。また、動作確認はLINQPadのDumpでリアルタイムに表示が可能です(asynchronousにクエリが走ってる最中はResultsのところにリアルタイムにグリッドが追加されていく!)

// Queries:(1)
client.ObserveRecordDynamic()
    .Select(x => new{ x.Name, x.Age })
    .Dump();
 
// Queries:(2)
client.ObserveRecordDynamic()
    .Where(x => x.Current == "Shibuya")
    .Select(x => new{ x.Name, x.Age })
    .Dump();
 
// Queries:(3)
client.ObserveRecordDynamic()
    .Buffer(TimeSpan.FromMinutes(5))
    .Select(xs => xs.GroupBy(x => x.Age).Select(x => new { Age = x.Key, Count = x.Count() }))
    .Dump();
 
// Queries:(4)
client.ObserveRecordDynamic()
    .Buffer(TimeSpan.FromMinutes(5))
    .Select(xs => xs.Max(x => x.Age))
    .Dump();
 
// Queries:(5)
client.ObserveRecordDynamic()
    .Where(x => x.Current == "Kyoto" && x.Attend[0] && x.Attend[1])
    .Buffer(TimeSpan.FromMinutes(5))
    .Select(xs => xs.GroupBy(x => x.User.Age).Select(x => new { Age = x.Key, Count = x.Count() }))
    .Dump();

5分間だったらBufferもしくはWindowが使えます(量が少なそうならBufferのほうが、後続クエリにLINQ to Objectsが使えて分かりやすい、量が多いならWindowで、同様にRxで集計クエリが書ける)。他に何ができるかはRxJavaのWikiのOperator一覧でもどうぞ。めちゃくちゃ何でもできます。

SQL vs Rx

SQLである必要は、あるようで、ない。テキストベースのDSLを作るならSQLが共通知識として期待できるので、SQLに寄せる必要性はかなり高い。けれど、Rxならば、LINQとしての共通知識と、C#そのものであるというコンパイルセーフな点と何でもできること、メソッドチェーン(+IntelliSense)による書きやすさ。SQLライクなものを使いたい理由は全くない。

(とはいえ勿論いちだいのRxがぶんさんごりごりのに勝てるとは思ってないんで、そこはまぁかじゅあるなはなしです)

TODO

というわけで見てきたわけですが、まあ所詮まだ単純なコードによるコンセプトレベルの話ですね!本格的にこれからやるとしたら

  • ObservableKinesisClientをもっとしっかりしたものに
  • Kinesis ApplicationをホストするためのServiceとプラグイン機構
  • ログ転送側としてSLABのKinesis用Sink

ですかねえ。まぁ、これらはJavaですでに用意されているamazon-kinesis-clientamazon-kinesis-connectorsを.NET環境で代替するために必要だ、といったところですね。素直にJava書けば?っていうのは一理あるけれど、どーなんですかね、C#でやりたいんですよ(笑)

Semantic Logging Application Block(SLAB)というのは構造化ロガー(正確にはロガーは含まれないけれど)と収集サービスがセットになったライブラリです。面白いのはOut-Of-Processでの動作が選べて、その場合はWindowsネイティブのEvent Tracing for Windows (ETW)経由でログが運ばれるので、非常に高速に動作する、というところ。Sinkというのは出力用プラグインみたいなものです。なので、アプリケーション→EventSourceロガー→SLAB Service(+ KinesisSink)→Kinesis という構造を作ることで、データをリアルタイムに投下するところまでは行ける。あとはRedShiftに送って解析(amazon-kinesis-connectorsには既にありますね)するなり、他のKinesis Application作るなりよしなに出来るかなぁ、できればいいかなぁ、と。ラムダアーキテクチャ、というホドデハ・モチロンナイ。

AWS + Windows(C#)

先週の木・金に開催されたAWS Summit Tokyo 2014にて、AWS + Windows(C#)で構築する.NET最先端技術によるハイパフォーマンスウェブアプリケーション開発実践と題して、セッションを行いました。

AWS + Windows(C#)で構築する.NET最先端技術によるハイパフォーマンスウェブアプリケーション開発実践 from Yoshifumi Kawai

まとめで書きましたが、C#+AWSは現実解、だと思ってます。そしてAWSだからといって特別なこともなく、そしてC#だからといって特別なこともない。Kinesisもちゃんと使えるわけだし、結構面白いことがまだまだ出来るんじゃないかな、って思ってます。なんでAzure使わないんですか?というのには、よく聞かれるのでお茶を濁して答えないとして(!)、AzureにもKinesisのようなAzure Event Hubsというものが先週プレビューリリースされました。C#からの活用という点では、こちらにも注目していきたいところです。Event Hubs Developer Guideなんか見ると普通に色々参考になるし、機能的にはHTTP以外にAMQP使えたり、ちょっと強そうではある。

Unity + iOSのAOTでの例外の発生パターンと対処法

Unity、はUnity3Dのほうの話ですが、それで開発していてiOS実機にデプロイして確認すると、以下の様なエラーに悩まされると思います!

System.ExecutionEngineException: Attempting to JIT compile method

ひぎぃ!怖い!これはiOSはネイティブコードしか許可していないので、MonoのAOT(Ahead-Of-Time)コンパイラ経由でネイティブコード変換されるんですが、それの関係で色々な制限があるからなのですね。さて、制限があるのはshoganaiんですが、引っかかるのは痛いです、めっちゃ痛いです、辛いです。

というわけで、どういうコードを書けば発生するのか、というのを並べてみました。どうすれば発生するのか分かれば、自然に避けられますからね。そのうえで、幾つかのものはちょっとしたハックで防げるので、それも述べます。あとは、一々実機で確認なんてやってられないので、効率のよい確認方法などなども紹介します。

Unity 4.5で少し改善されたとか言ってましたが別にあんま改善されてる気配なくて以下のコードは4.5.1で確認取って全部片っ端から死にますんで安心してください、悲しい。

Interlocked.CompareExchange

正確にはInterlocked.CompareExchange<T>が死にます。以下のコードは即死。

// ExecutionEngineException: Attempting to JIT compile method '(wrapper native-to-managed)' while running with --aot-only
var a = "hoge";
Interlocked.CompareExchange<string>(ref a, "hugahuga", "hoge");

ExecutionEngineExceptionの中でもnative-to-managedと出ているものは対処方法が明確で、そもそもUnityのトラブルシューティングのiOSのところにも書いてあります。デリゲートに[MonoPInvokeCallback]が必要だ、と。つまりそういうことで、mscorlib.dll内のメソッドなので手が出せないので、100%死ぬ運命にあります、南無。対処方法は使わないこと。(実際にはそれだけじゃなさそうですが、中のことで分からないのでとりあえずそういうことにしておこふ)

ただし、実はCompareExchangeにはintやdoubleなどを受け取るオーバーロードがあって、そちらは大丈夫です。問題なのは<T>のオーバーロードだけなのです。しかもCompareExchangeにはobjectを受け取るオーバーロードもあるので、そちらを使うことによりT的なものも一応回避することが可能。どうしても使いたい場合は安心してどうぞ。

// これは大丈夫!
object a = "hoge";
var v = Interlocked.CompareExchange(ref a, "hugahuga", "hoge");

ちなみにInterlocked.CompareExchange<T>は意外なところでも使われていて、というか、VS2010以降のコンパイラでeventをコンパイルすると、eventの実装がInterlocked.CompareExchange<T>を用いたものになっています。なのでプラグインとしてdllを作ってUnityに読み込ませると、これに引っかかって死にます。回避方法はなし。event使うのやめましょう、Actionで我慢しましょう。なお、Unity内だけで使う分には古いコードが吐かれるので問題ないです。(あとufcppさんからコメント貰いましたが、add/deleteといったカスタムイベントアクセサを定義すれば回避できるもよふ)

動的コード生成

Reflection.Emitとか、この辺は当たり前だ!ですね。

Expression<Func<string>> expr = () => "hoge";
 
// System.ExecutionEngineException: Attempting to JIT compile method '(wrapper dynamic-method) System.Runtime.CompilerServices.ExecutionScope:lambda_method (System.Runtime.CompilerServices.ExecutionScope)' while running with --aot-only.
expr.Compile();

Expressionも構築まではOKだけどCompileはNG。悩ましいのは一般的にC#で高速化を測る場合(特にシリアライザ)って動的コード生成+キャッシュをよく使います。neue cc - C#での動的なメソッド選択における定形高速化パターンとかneue cc - Expression Treeのこね方・入門編 - 動的にデリゲートを生成してリフレクションを高速化をミテネ。が、動的コード生成が使えないと低速なリフレクションのみかぁ、うーん、萎える。といったかんぢ。こういうのが積み重なってC#が遅いとか言われると心外だなぁ、UnityのC#は正直、うーん、ねぇ……。

PropertyのReflection

そんなわけでリフレクション。これがひじょーに悩ましくて、どこまでが死んでどこまで大丈夫なのかがひじょーーーーーに分かりづらい!さて、実は意外と行けますが、そして意外と死にます。

// こんなクラスがあるとして
public class MyClass
{
    public int MyInt { get; set; }
    public string MyStr { get; set; }
}
 
// ----
 
var mc = new MyClass() { MyStr = "hoge", MyInt = 100 };
var propInfo = typeof(MyClass).GetProperty("MyStr");
 
// SetValueは大丈夫
propInfo.SetValue(mc, "hugahuga", null);
 
// GetValueは死ぬ
// System.ExecutionEngineException: Attempting to JIT compile method '(wrapper delegate-invoke) System.Reflection.MonoProperty/Getter`2<>:invoke_string__this___MyClass ()' while running with --aot-only.
var v = propInfo.GetValue(mc, null);

(GetValueは死ぬ、って書きましたがUnity 4.5.1 + iOS7.1で試したら死ななかった、↑が死んだのはmono2.6.7でした)。なんだこの非対称ってところですが、実際そうだからshoganai。そしてGetValueは実は簡単に回避できます。GetGetMethodでメソッドを取得して、それをInvokeすればいい。

var mc = new MyClass() { MyStr = "hoge", MyInt = 100 };
var propInfo = typeof(MyClass).GetProperty("MyStr");
 
// こうすればGetもできる
var v = propInfo.GetGetMethod().Invoke(mc, null);
Debug.Log(v);

というわけで、これでシリアライザも作ることができます。例えばこんな感じの簡易シリアライザ。

public static void Run()
{
    var format = Serialize(new MyClass { MyInt = 100, MyStr = "hoge" });
 
    var v = Deserialize<MyClass>(format);
    Debug.Log(v.MyStr + ":" + v.MyInt);
}
 
public static string Serialize<T>(T obj)
{
    // JSON、ではない
    var sb = new StringBuilder();
    foreach (var item in typeof(T).GetProperties())
    {
        sb.Append(item.Name + ":" + item.GetGetMethod().Invoke(obj, null));
        sb.Append(",");
    }
    return sb.ToString();
}
 
public static T Deserialize<T>(string format)
{
    var obj = Activator.CreateInstance<T>();
    var type = typeof(T);
 
    foreach (var item in format.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Split(':')))
    {
        var key = item[0];
        var value = item[1];
 
        var propInfo = type.GetProperty(key);
        if (propInfo == null) continue;
 
        // 型の変換は超絶手抜き:)
        if (propInfo.PropertyType == typeof(int))
        {
            propInfo.SetValue(obj, int.Parse(value), null);
        }
        else
        {
            propInfo.SetValue(obj, value, null);
        }
    }
 
    return obj;
}

これはインチキなテキスト形式にシリアライズしてますが、例えばJSONにシリアライズ・デシリアライズとかできるようにすれば、ひじょーに有益でしょう。ベタリフレクションとかC#er的には萎えるんですが、まぁその辺はshoganaiということで諦めるぐらいはできる。諦めます。

InterfaceとGenericsとStruct

この3つが組み合わさることによって端的に言えば、死ぬ。

// こんなインターフェイスとメソッドがあるとして
public interface IMyInterface
{
    void MyMethod<T>(T x);
}
 
public class MyImpl : IMyInterface
{
    public void MyMethod<T>(T x)
    {
    }
}
 
IMyInterface intf = new MyImpl();
intf.MyMethod("hogehoge"); // 死なない
 
// System.ExecutionEngineException: Attempting to JIT compile method 'MyImpl:MyMethod<int> (int)' while running with --aot-only.
intf.MyMethod(100); // 死ぬ

ジェネリクスのメソッドをインターフェイスで受けて、構造体を渡すと死にます。死にます。クラスなら死なないんですけどねー。さて、しかしこの現象は回避する術があります。

// どこでもいいし呼び出さなくてもいいから、使う構造体の型を呼ぶコードをどっかに書いておく
static void _CompilerHint()
{
    new MyImpl().MyMethod(default(int));
}
 
void Awake()
{
    IMyInterface intf = new MyImpl();
    intf.MyMethod(100); // ↑により死なない
}

実体で実際に使う型を用いて呼び出してるコードを書いておくと死なずに済みます。実際に呼び出す必要はなくて、とにかく書いてあればいいです。イメージとしてはコンパイラにヒントを与えるような感じ。なのでまぁ、1. インターフェイスで受けないようにする 2.受けなきゃならないシチュエーションがあるなら(まぁそりゃあるよね)どっかに定義沢山書きだしておく。ことにより神回避。オマジナイのようでいて実際効果あるからshoganai。

あと、ジェネリクスはメソッドじゃなくてインターフェイスのほうがTなら死にません。IMyInterface<T>みたいなほう。

LambdaとGenericsとStruct

Genericsのラムダ作って構造体渡すと死にます、例によって渡すのがクラスなら死にません。

// こんなメソッドがあるとして
static void Death<T>()
{
    var act = new Action<T>(_ => { Debug.Log("hoge"); }); // ここではまだ死なない
 
    // System.ExecutionEngineException: Attempting to JIT compile method '<Death>b__0<int> (int)' while running with --aot-only.
    act(default(T)); // 呼び出すと死ぬ
}
 
// こんなコード呼び出しすると死ぬ
Death<int>();

こんな入り組んだコード書かないって?いや、案外このパターンに当てはまっちゃったりしたりするんですよ。特にライブラリ書いたりする人だとラムダ式の使いどころによっては、こういうパターンになりがちで頭抱えます。解決策はラムダ式使うのやめよう!じゃあなくて、簡単な解決策があります。

static void Death<T>()
{
    var _dummy = 0;
    var act = new Action<T>(_ => 
    {
        Debug.Log("hoge");
 
        _dummy.GetHashCode(); // なんでもいいから外側の変数をキャプチャする
    });
 
    act(default(T)); // 死なない
}

ラムダ式は外側の変数をキャプチャするかしないかによって、生成されるコードが変わってきます。そこがミソで、勿論キャプチャしないほうが本来は効率がいいんですが、AOTで死んでしまっては元も子もない。キャプチャすることによってAOTで死なないコードが生成されます、というわけで、入り組んだシチュエーションでラムダ式使いたい場合は意図的に外側の変数をキャプチャすることで回避できます。これは思いついた時は思わず叫んじゃいましたね!マジで!(そんだけこの問題に悩まされてたんですよ……)

参照型で死ぬ

型引数がクラスなら死ぬことはない、と思っていた時もありました。残念ながら、死ぬ時があるんですねぇー。いや、正確にはclass+structで死ぬ、なんですが、struct+structだと死なないのが癪。これは後述しますがLINQのSumがクラスで死ぬ理由が分からなくて再現コード作ってたらこうなったって感じです。よくわからないけど、こうなった。

public static void Run()
{
    // 参照型でメソッドを呼ぶ
    // System.ExecutionEngineException: Attempting to JIT compile method 'Method2<int, object> ()' while running with --aot-only.
    Method1<object>();
}
 
// 1型引数でメソッドを呼ぶ際に片方が値型
public static void Method1<T1>()
{
    Method2<int, T1>();
}
 
// 2型引数で戻り値がある(戻り値の型はなんでもいいけどvoidはダメ)
static string Method2<T1, T2>()
{
    return "";
}

ちなみにMethod1<int>みたいに、struct渡すんなら動くんですよね、逆にこれは。クラスだと死ぬ。どうしてこうなるのか、ちょっとこれはよくわからないですね、ともかくクラスでも油断すると死ぬということはよくわかりました、あべし。

LINQで死ぬ

UnityのiOSビルドで使うとエラーになるLINQ拡張メソッドのメモといった記事もありますが、実際死にます。これは濡れ衣みたいなものなんですけどねぇ、別にLINQが悪いわけじゃないし、それでLINQ使わない!LINQ禁止!とか絶対言って欲しくないです。LINQのないC#なんてC#じゃないです。C#の魅力の8割はLINQなのですから。と、それは置いておいて、実際幾つかのLINQのメソッドは死にます。

例えばAverage(selector)。

// System.ExecutionEngineException: Attempting to JIT compile method 'System.Linq.Enumerable:<Average`1>m__20<int> (long,int)' while running with --aot-only.
Enumerable.Range(1, 3).Average(x => x);

なんで死ぬのかというと、ソース見れば簡単に分かります。Unity-Technologies/monoからEnumerable.csの該当行を見ると

return source.Select (selector).Average<int, long, double> ((a, b) => a + b, (a, b) => (double) a / (double) b);

お分かりかな?そう、「LambdaとGenericsとStruct」のところで見たように、Genericsのメソッドの中で値型のラムダが放出されてます。そう、結構あるんですよ、Genericsのメソッドの中にラムダを埋めてしまうのって。さて、で、これは死にます。具体的に死んだ箇所は、エラー履歴の一番上のat…のとこ見れば

// at System.Linq.Enumerable.Average[Int32,Int64,Double] (IEnumerable`1 source, System.Func`3 func, System.Func`3 result) [0x00000] in <filename unknown>:0

privateメソッドのAverage(↑でいうAverage[int, long, double]のとこ)で死んでるのが分かります。基本的に呼び出すタイミングで死ぬのでfunc(total, element)ってとこが死亡地点だと推測付きます。

これの対処方法は?ないよ!System.Core.dllの中のコードだから手が出せません。もはや使わないしか選択できません!もしくは、自前実装してAverageSafeとかって拡張メソッドを用意するとか、ですかねえ。それも悪くはないと思います、shoganaiし。

で、実はこの問題は当然mono本体は気付いていて、mono 2.8では改善されています。該当コミットを見れば、ラムダ使って共通化されてるコードがコピペに置き換えられてます:) これがAOTセーフだ!みたいな。はい、ライブラリには苦労してもらいましょう、使う側が快適ならそれで、それがいいのです。

残念ながら現在のUnity(4.5.1)のmonoは2.6で、しかも2.8へのUpgradeは蹴られてます。3.0(そう、monoの最新はもう遠いところにある、Unity置いてかれすぎ)へのアップデートは、もしやる気があるとしても大仕事になるだろうから、当面は来そうにないですねえ。でもmono 2.8で改善されたのって4年前なんですよね、4年前から変わらずLINQ(の一部)が使えないUnity……、残念です。とりあえずダメ元でEnumerable.csだけでもバージョンあげてくれ!ってFeedbackを出したので、よければVoteしてください。Upgrade Enumerable.cs for avoid AOT Problem of LINQ(Average etc…)

XamarinのほうはAOTに関しても先を行っているようで、詳しくはXamarinの中の人である榎本さんのインサイドXamarin(6)の真ん中辺りに書いてあります。最新のXamarinと昔のXamarinと、そしてUnityとではAOTの制限がそれぞれ微妙に違っててなんとも。しかしXamarin、じゅる、いいなぁ……。

UnityのLINQでは他にも明らかに使えないメソッドがあって、例えばThenBy。

// System.ExecutionEngineException: Attempting to JIT compile method 'System.Linq.OrderedEnumerable`1<int>:CreateOrderedEnumerable<int> (System.Func`2<int, int>,System.Collections.Generic.IComparer`1<int>,bool)' while running with --aot-only.
Enumerable.Range(1, 3)
    .OrderBy(x => x)
    .ThenBy(x => x)
    .ToArray();

死にます。これも最新のmonoでは解決しています、該当ソース

#if FULL_AOT_RUNTIME
			var oe = source as OrderedEnumerable <TSource>;
			if (oe != null)
				return oe.CreateOrderedEnumerable (keySelector, comparer, false);
#endif

これは「InterfaceとGenericsとStruct」のとこに書いた制限を回避してます。IOrderedEnumerableというインターフェイスのままCreateOrderedEnumerableを呼ぶと死ぬので、OrderedEnumerableにキャストして具象型に戻すことによってうまく動くようにしています。ThenByは便利なので使いたいものですねえ(まぁ富豪な処理なのでゲーム向けかと言われるとビミョーですが)

最後に、Sumは参照型でも死にます。逆に値型だと生き残れます。

// System.ExecutionEngineException: Attempting to JIT compile method 'System.Linq.Enumerable:Sum<object, int> (System.Collections.Generic.IEnumerable`1<object>,System.Func`3<int, object, int>)' while running with --aot-only.
Enumerable.Empty<object>().Sum(x => (int)x);

これは「参照型で死ぬ」パターン、ソースコードの該当箇所を見ると……

public static int Sum<TSource> (this IEnumerable<TSource> source, Func<TSource, int> selector)
{
	Check.SourceAndSelector (source, selector);
 
	return Sum<TSource, int> (source, (a, b) => checked (a + selector (b)));
}
 
static TR Sum<TA, TR> (this IEnumerable<TA> source, Func<TR, TA, TR> selector)
{
	TR total = default (TR);
	long counter = 0;
	foreach (var element in source) {
		total = selector (total, element);
		++counter;
	}
 
	return total;
}

これねえ、なんで参照型で死ぬのか本当にさっぱり分からなかったけど、とりあえずSum<TSource, int>で二回層掘ってるのが死因っぽいです、片方はint固定で、二階層目は戻り値TRっていう。Max, Minが参照型のみ死ぬもの同じようなコードだった。これで死ぬとかもはや理不尽さしか感じなくて怖い怖い。ちなみにmonoの最新版のコードではメソッドは一回層で重複上等のハイパーコピペになってます(勿論それによりExceptionは発生しなくなる)、それでいいです、はい、ほんと。

そんなわけでLINQは一部の地雷メソッドに注意しながら使う!まぁ、それはそれでいいんですが(地雷が怖いから使わないってのはNG)、やっぱ地雷が埋まってるのは怖い。というわけで、Unityのmonoのランタイムが新しくなってくれるのが一番なのですが現実はそれを待ってはいられないので、mono本体のEnumerable周辺コードを頂いて、名前空間だけ、例えばSystem.LinqExとかにして、基本そちらをusingするようにするっていう風にして回避するのがいいんじゃないかしら、というか私はそうしてます。この辺は名前空間の切り分けだけでなんとかなる拡張メソッドの良さですね。

Enumで死ぬ

簡単にはEnumの配列をToArrayすると観測できる!

// こんなEnumがあるとして
public enum MyEnum
{
    Apple
}
 
// ToArrayで問答無用で死ぬ
// System.ExecutionEngineException: Attempting to JIT compile method '(wrapper managed-to-managed) MyEnum[]:System.Collections.Generic.ICollection`1.CopyTo (UniRx.MyEnum[],int)' while running with --aot-only.
new[] { MyEnum.Apple }.ToArray();

(wrapper managed-to-managed)ってのが目新しくていいですね!これの対処方法は、元が配列とかListだと印象的にヤヴァいので空のイテレータに変えてやります、それもご丁寧にジェネリクスじゃないIEnumeratorを経由することで、なんとなく回避できます。

public static class AotSafeExtensions
{
    // こんなメソッドを用意しておくと
    public static IEnumerable<T> AsSafeEnumerable<T>(this IEnumerable<T> source)
    {
        var e = ((IEnumerable)source).GetEnumerator();
        using (e as IDisposable)
        {
            while (e.MoveNext())
            {
                yield return (T)e.Current;
            }
        }
    }
}
 
// 死なない!
new[] { MyEnum.Apple }.AsSafeEnumerable().ToArray();

ヤヴァそうな香りがしたらAsSafeEnumerableを呼ぶ、という対処療法で勝つる。かなぁ……?

実機を使わないでAOTのテストする方法

ここまでで例外の発生パターンと対処法は終わり。じゃあ実際、こういった問題をどう検出するか、ひたすら実機テスト?というのも辛い。で、AOT自体はmono本体にもあって、そして現在のUnityはmono 2.6相当です。というわけでmono 2.6でAOTを動かせばいいんじゃろ?mono –full-aot hoge.exeと書くだけで、iOS実機とほぼほぼ同等のAOT例外が検出できます(この記事の範囲だとInterlocked.CompareExchange以外は同じ)。MonoBehaviourとかは無理ですがロジック系だったらNUnitでユニットテスト書いて、回すことで自動テスト可能になります。

実際、私はこの記事を書くにあたって、Windows + Visual Studio 2013でC#を書いて.exe(ConsoleApplication)作って、それを会社の同僚の作ってくれたexeを渡すとfull-aotで実行して結果表示してくれるウェブサービスに突っ込んで延々と動作確認してました。超捗る。むしろ同僚が神だった。実機とかやってられない。そもそもUnity書くのもVisual Studioじゃなきゃ嫌だ(UnityVS - Unity開発におけるVisual Studio利用のすすめ)。

UniRx

なんで延々と調べたかというと、今、私はUniRx - Reactive Extensions for Unityというライブラリを作っていて、というか実際アセットストアにも既に公開されているんですが(無料です!)、例によってiOSで動かなくて!で、重い腰を上げて調べたのでした。パターンさえ分かってしまえば、まあ十分対応できる範囲ですねー、というわけでバシバシと動かなくなる箇所を殺してる最中です。

UniRx自体はブログ記事をそのうち書く書く詐欺で(一応、ちょっとだけ発表した時の資料はある)、ええと、AOTの対処が終わったら書く!というのと、【第1回】UnityアセットまみれのLT大会でLTするつもりなので、そちらでもよろしくお願いします。というか是非いらしてください、お話しませう。

OWINの仕組みとOWIN上のフレームワーク(ミドルウェア)の作り方

2014/2/8に北海道のCLR/HでOwinについて話してきたんですが、なんと!今の今までスライド公開してなかった!これはひどい!3ヶ月放置してた!熟成肉!ウルフギャング!ということでやっとこさ公開。若干加筆してあります。

How to Make Own Framework built on OWIN from Yoshifumi Kawai

前半はOWINとは何か、というのとキーワードや仕組みについての解説。後半はLightNodeという私の作っているWebAPIフレームワークの実装を通して、フレームワークに必要な要素と実装例、そして性能の出し方を見て行きましょうという感じです。

発表した時は、失敗した感があって公開できないでいたんですが、読み直すと結構いいこと書いてありますね、とか自画自賛。あんましこういう内容のセッションってないですし、いい感じなんじゃないでしょうか、改めて読むと。こういう内容も割とレアいですしね、レア度大事。後半部分がじっくり読む系な内容になっていて、あんまし発表向けじゃなかったのは良くなかったかな……(資料を当日のその場で作ってて、どう話すか、どう見せるかについて考えてこなかったのが悪いというところもあるというかかなり悪いのでその辺は大反省)

Demo Walkthrough

あ、そうそう、LightNodeもver.3になってます。今回からUnity用のジェネレータが追加されているのと、それと何度かデモやってて手間取った箇所があったので、スムーズに行えるようにデフォルトパラメータ類を調整しました。この辺、実際にやってくの大事ですねえと実感。そんなわけで1から見て行きましょふ。ちなみに画像は全部英語ですが、日本語のスクリーンショット取るのが面倒だっただけなので、実際は別にちゃんと日本語です。

まずVSをを立ち上げて新規プロジェクト作成します。そしてASP.NET Web Applicationを選択。

そのまま、空のプロジェクトを選択します。

さて、これで空のASP.NETプロジェクトが出来ました。続いてOwinでホストするためNuGetを開いて「Microsoft.Owin.Host.SystemWeb」をインストール。

そのままServer用のLightNode、「LightNode.Server」をインストール。

これでDLL参照はオシマイ。まだファイルがゼロなので、追加していきましょう。まずはOwinとLightNodeの利用を関連付けるため、OWIN Startup Classを追加します。このテンプレートはVSに用意されてるので、Add→New Item→OWIN Startup Class。

そしてそのファイルに、app.UseLightNode();の一行だけ足します。

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        app.UseLightNode();
    }
}

これで準備は完了です!最後に実際のAPIを作りましょう。Add→ClassでCalc.csというのを作ります。そしてそのファイルに以下のコードを書いてください。

public class Person
{
    public int Age { get; set; }
    public string Name { get; set; }
}
 
public class User : LightNode.Server.LightNodeContract
{
    public Person Show(string name, int age)
    {
        return new Person { Name = name, Age = age };
    }
}

これで最初のAPIの作成は完了です。Ctrl+F5で実行し、 /User/Show?name=john&age=20 にアクセスしてみてください。例えば http://localhost:8708/User/Show?name=john&age=20 。JSONが表示されたことを確認できるでしょう。このように /クラス名/メソッド名?パラメータ が露出するAPIとして、 戻り値の型がJSONとなります。Web APIを実装する手段として、限りなく最小の手順です。

最後に、OWINとは関係ありませんが、実際にインターネットへの発行、Azure WebSitesでホストしてみましょう。WEBサイトを作成し、発行プロファイルをダウンロードします。

次にソリューションファイルを右クリックし、発行。ダイアログでダウンロードした.publishファイルを選択すると、発行先、ユーザー名、パスワードが入力されています。

必要ならば発行の設定で、Remove additional files at destinationもチェック入れておきましょう。

そしてPublishボタンを押せば、Webへの公開は完了です。例えば http://lightnodedemo.azurewebsites.net/User/Show?name=Mary&age=30 といったところに!5分あれば全部デモしきれます!(と言いつつ毎回どっかではまって10分かかってる説)

Demo Client

というわけでWeb API作成まではそんな感じなのですが、LightNodeのもう一つの特徴にクライアントサイドの自動生成があるので、引き続きそれを見て行きます。

まず、新規にクラスライブラリとしてShareDataを作り、WebApplicationに作っていたPersonクラスを移します。また、ConsoleApplicationも同じソリューションに作り、WebApplicationとConsoleApplicationがShareDataを参照するようにします。

次にConsoleApplicationのNuGetを開いて、LightNode.Client.PCL.T4とLightNode.Formatter.JsonNetをインストールします。

この状態になったら、まずWebApplicationをビルド(Personクラスを移した影響でコンパイルエラー出たら適宜名前空間をusingしたりしてね)。そしてConsoleAppのほうにインストールされたLightNodeClient.ttを開いて、先頭行を、WebApplicationのdllのパスに修正します。

<#@ assembly name="$(SolutionDir)\WebApplication4\bin\WebApplication4.dll" #>

するとドバッとコードが生成されてるはずです!はずです!

このクライアントコードは、HttpClientベースで全て非同期なモダンな形態になっています。ということでSystem.Net.Httpの参照が必要なので、それも参照しておきます(ConsoleApp以外の場合はNuGetからHttpClientを参照する)。

あとはProgram.csに以下のように書いてもらえれば通信できます。

async static Task Run()
{
    var client = new LightNode.Client.LightNodeClient("http://localhost:8708");
 
    // client.Class.MethodAsync(argument)
    ShareData.Person person = await client.User.ShowAsync("John", 30);
 
    Console.WriteLine(person.Name + ":" + person.Age);
}
 
static void Main(string[] args)
{
    Run().Wait();
}

Server側にあるクラス名.メソッド名Async(引数)というのが、(自動生成なので当たり前ですが)型付けされて、戻り値も自動でデシリアライズされるといった、自然な操作で扱えます。サーバーAPIの数が増えれば増えるほど大変だし、修正した時も再生性するだけなので、色々楽ちんですよね、と。

ASP.NET vNext

ちょうどTechEdが開催されていて(海の向こうの話です!)、Introducing ASP.NET vNextとか、わくわくするような話も出てきましたん。さて、LightNodeはWebAPIなのですが、100% WebAPIじゃなくてHTML返したいといったシチュエーションもあると思います。簡単なものならRazor EngineやT4 Templateでstringを返せばいいでしょふけれど、数が多かったりする時は、UseMvc()でASP.NET MVCと共存すりゃあいいかなー、って思っています。全然問題ない。そういった共存もまたOWINのモデルかな、って思ってます。

最後に

そうそう、OWIN使っているからカッコイイ、なんてことはないし、軽量なものならNancy使えばモダンでイケてる、なんてことも全くありません。私だったらビューが必要なものの実装は絶対にASP.NET MVC使うし、APIならLightNodeで書きます。当たり障りのないことをいえば、トータルのバランスで優れたものを選択できる嗅覚が大事ってところですね。とはいえまぁ、その選択の基準を作るためにも、OWINについてしっかり知っておく必要はあるでしょう。間違いなく。

OWIN、ASP.NET vNextと、取るべき選択肢が多くなってきました、からこそ、しっかりした判断が求められるので、逆に言えばシビアです。シビアですが、だからこそ面白い、面白くなってきた!って私は思ってます。ね、こういうのを楽しみましょう。イイ時代です。

Visual Studio Tools for Unity(UnityVS) - Unity開発におけるVisual Studioのすすめ

追記:Microsoftが買収してVisual Studio Tools for Unityとして無料でリリースされました、やったね!

Unityで開発するにあたってエディタは何を使っていますか?といったら、勿論Microsoft Visual Studio!というわけで、VSとUnityを統合してコーディング&デバッグを可能にしてくれるUnityVSの紹介をしたいと思います。とにかく素晴らしいので、超オススメ。ちなみに最新のVS2013にも勿論対応していますよ。

ちょうどUnite Japan 2014で、UnityVS作者のJb Evain氏が「Unityゲーム開発へのVisual Studio導入」というセッションを行い、勿論喜んで聞きに行った!感動した!ので、その講演をベースに紹介したいと思います。講演聞く前からUnityVSは使っていたのですが、改めて超良いなー、と、むしろもっと利用者を増やさなければ!という義務感がですね、はい。

Unityのスクリプト開発環境

UnityScriptの、じゃなくて.csとか.jsを何で書くか、のお話。

  1. MonoDevelop

標準同梱、色々なプラットフォームで動くし、ちゃんとIDEなので決して悪くない。が、Unityについてくるバージョンは古い場合が多く、そのためバグが残っていたり。また、日本語の入力が大変問題が多く叫ばれていたりする。

  1. 外部のテキストエディタ

SublimeやVim、Emacsなど。特にSublimeはよく使われているよう。非常に軽快で悪くない、とはいえ、IDEの持つ多くの機能を当然持っていないわけで、機能としては劣るといえる。SublimeはSublimeSocketAssetを入れると補完(弱)とかエラー表示とかは少しまかなえるようですけれど、Vimとかもそうですね、頑張れはするのだけれど、最終的にIDEに及ぶかというと、まあ、頑張れる。頑張れはする。

  1. Unityエディタ上のスクリプトエディタ

UnIDEとか。カジュアルな編集にはベンリだけれど専用に使っていけるか、というと……。

  1. Visual Studio

Windows専用。有料、安くない。とはいえ機能は最強。

そんなわけでVisual Studioを選べるのなら選ぶべき!なのだけれど、単純にUnityのエディタとしてVSを使うと幾つかの問題がある。

VSはMicrosoft .NET用でありUnity向けではない
VSのプロジェクト構造はUnityとミスマッチがある
2つのツール(Unity-VS)間でやりとりが取れない
デバッガーが動かせない(動かしにくい)

と、いった問題をUnityVSは解決し、両者を完全に統合してくれる。

UnityVSの機能

  • Unity Project Explorer

勿論、VSのソリューションエクスプローラーも使えますが、このUnity Project ExplorerはUnityのProjectウィンドウと同じ見た目を提供してくれるので、こっちのほうが選びやすい場合はこっちが使えます。

  • C#/Boo/UnityScriptのシンタックスハイライト/入力補完。

C#だけじゃなくてBoo, UnityScriptにも対応。普段はC#使ってても、AssetStore経由でBooやUnityScriptを取得することもあるだろうし(Booはあるのかなあ?)、全対応は良いこと素晴らしい。日本語のコメントを使うことも出来るし、入力補完等はVisual Studioそのものなので完璧。

  • メソッド生成ウィンドウ

MonoBehaviourのOnMouseEnterなどの雛形が直ちに作れるので、ついつい忘れがち/ミスりがちな名前をリファレンスからコピペったりしなくても作れる。画像のような大きなウィンドウの他に、その場でテキストベース補完でササッと作れるQuick MonoBehaviours Windowもあり。

  • リファレンスの統合

クラス名やメソッド名を選択してヘルプ→Unity API Reference、もしくはショートカットキーでVS上でその場でリファレンスが引けます(VS内ウィンドウで開かれる)。ベンリベンリ。

  • デバッグ

F5押すだけでデバッガをUnityにアタッチできる、当然動いてる最中はVSのデバッグ機能がフルに利用可。ローカルウィンドウもウォッチウィンドウも、ステップ実行も全て。(ただしCoroutineの中では挙動がかなり怪しくなるのでそこだけは注意)。

  • 外部DLLサポート

外部DLLを参照した場合でも、デバッガでしっかりとステップ実行できてとても嬉しい。あと、Visual Studioで開発できることの嬉しさに、サーバーサイド(C#/ASP.NET)とUnityのプロジェクトを同じソリューションに突っ込めるところがあったりします。移動が簡単だし、通信用データや幾つかのロジックが共有できたりする。サーバーサイドはPHPで開発なんておかしいよ!全部C#で書くんだよもん。例えば以下のような構成を取ってみる。

私がCTOを務めるグラニは、現在のところウェブベースのソーシャルゲーム(神獄のヴァルハラゲート、今CMやってますん)を提供していて、それはC# 5.0 + Windows Server 2012(IIS 8.0) + ASP.NET MVC 5で動いていたりします。サーバーサイドをC#で開発するのは得意領域なので、そのままにクライアントサイドとC#で融和出来れば、開発効率相当良い……!といったことがUnityVSならシームレスに実現できて素晴らしい。

実際、そうしたサーバーAPIをC#で書いて、そのメタデータを元にUnityの通信クライアントを自動生成、送受信データはサーバー側とクライアント側で共有するの前提にしたWebAPIフレームワークLightNodeというのを作ってます。(ところで絶賛エンジニアも募集してます←求人←宣伝)。

共有用のクラスライブラリは、UnityVS入れるとUnity用プロファイルで作れるのも嬉しい。

ちなみに、見た目上はVisual Studioのソリューションに収まっているとはいえ、Unityのシステム的に、VSのプロジェクト参照は無効なのは注意(参照してもUnity側でリロードすると消えちゃうの)。ルール通り、Assets下にDLLを配置する必要があります。それに関してはUnityVSのドキュメントDLL Debuggingで触れられてますが、ビルド後にAssetsにDLLを配置するように仕込むと良いもよう。例えば以下のようなものをShare.csprojに足してやればOK。

<!-- 実際にはDebugビルドとRelaseビルド分けるのもいれよふね -->
<Target Name="AfterBuild">
    <ItemGroup>
        <CopySource Include="bin\Debug\*.*" />
    </ItemGroup>
    <Copy SourceFiles=" @(CopySource)" DestinationFolder="$(SolutionDir)\Assets\External\" SkipUnchangedFiles="true" OverwriteReadOnlyFiles="true" />
</Target>

利用感としては、まぁまぁ悪くないです。メソッドの参照に飛ぶとDLLのメタデータを参照しに行ってしまうのが残念ではありますが。あとは.slnの場所がUnityプロジェクト下になってしまって構成がいびつなのがちょっとだけ辛い、かな?その辺の生成はカスタマイズできる - Project File Generationっぽいんだけど、上手くいかなくて今のところ断念中。

ということで

Unityの開発って結構Macで行ってる人が多いのですね。実にイマドキ……。それでも、スクリプティング環境はVisual Studioがベストだと思うんだなあ。VMにWindows入れてでもなんでも使ったほうが圧倒的に捗る、はず。少なくともエディタで苦労するよりは百億倍。ちなみにRemote Debuggingもあるようですよ。

それでもVisual Studioは高い……?うーん、そもそもUnity Proのほうがずっと高いんですが(VS Proは単品をふつーに買って6万ぐらい、色々な購入モデルがあるので実質もっと安くはなるかな)、それはおいといて、私がBuild Insiderに寄稿した記事でスタートアップ企業にマイクロソフト製品の開発ライセンスが無償提供されるBizSparkプログラム活用のススメ(タイトルクソ長い!)で紹介しているBizSparkというプログラムでは、設立5年未満の企業ならVisual Studioを無償で利用することが可能です。実際BizSparkはとても良い、助かる、助かってた。学生さんならDreamSparkという同様の学生支援プログラムがあります。

C#自体、非常に良い言語なのですが、Visual Studioと合間れば相乗効果で数倍数十倍に更に良くなるので、(たとえUnityのC#が古いバージョンだったりiOSのせいでAOTで苦しんだりしつつも)、良きC#生活を満喫して欲しい/したいところですねー。

ForEachAsync - 非同期の列挙の方法 Part2

Part2って、Part1はあったのかというと、うーん、非同期時代のLINQ、かな……?さて、今回はForEachがテーマです。といってもそれってSelect+WhenAllでしょ!「Selectは非同期時代のForEach」って言ってたじゃない、というと、はい、言ってました。まだ他に言うことあるの?というと、例えば以下のシチュエーション。

var httpClient = new HttpClient();
var tasks = Enumerable.Range(1, 100000)
    .Select(async x =>
    {
        var str = await httpClient.GetStringAsync("http://hogehoge?q=" + x);
        Console.WriteLine(str);
    });
await Task.WhenAll(tasks);

別に動きはしますが、制御不能に10万件、同時リクエスト走ります。これはまぁいくないですよね。もはや途中で死んだりしますので動くとも言えない……。というわけで、元シーケンスが巨大な時は、Select+WhenAllはForEachになりえないのです。

さて、この事態に手抜きで対抗すると?

var httpClient = new HttpClient();
Parallel.ForEach(Enumerable.Range(1, 100000), x =>
{
    var str = httpClient.GetStringAsync("http://hogehoge?q=" + x).Result;
    Console.WriteLine(str);
});

みんな大好きParallel.ForEachです。CPUバウンドとかI/Oバウンドとか面倒くさいんですよ、動きゃあいいんですよ(ホジホジ。という楽さ。実際これは普通に機能します。ので、バッチとかはこんなんでもいーんじゃないでしょうか、マジで。でも、これ、序盤はじわじわと並列数が上がってくので、初速がイマイチに感じるかもしれません。最初はコア数分しか並列にならず、待ちが多いことを検出してからじわじわ上がっていくので。あと終盤の挙動をアレゲに感じたりするかもしれません。待ち時間が長いと、際限なく並列数が上がってっちゃうんですよ。でも別に極端に上がっても速くなるわけじゃなくて、逆にむしろ余計遅くなる。

※これは別に作り話じゃなくて、私はプロダクション環境で実際に数十万リクエストを叩くコードを走らせていて、常に同時並列数やスレッド消費量のモニタ取って、調整いれてます。

どう調整入れるか、というと……

// 最小スレッドプール数を最初に適当に伸ばしてやると初速に効く
// 設定は一回でいいので、アプリケーションスタートアップのところにでも置いときましょう
ThreadPool.SetMinThreads(200, 200);
 
// 無尽蔵に伸び続けるのもいくないのでMaxDegreeOfParallelismを設定
var httpClient = new HttpClient();
Parallel.ForEach(Enumerable.Range(1, 100000), new ParallelOptions { MaxDegreeOfParallelism = 200 }, x =>
{
    var str = httpClient.GetStringAsync("http://hogehoge?q=" + x).Result;
    Console.WriteLine(str);
});

SetMinThreadsとMaxDegreeOfParallelism、この2つはふとぅーに影響大きくて大事。なので適当に、とか書きましたがあんまり適当にやるのはよくない。

ForEachAsync

とはいえ、非同期は非同期として扱いたい!そりゃそーだ。で、つまり、ようするに、同時実行数を抑えながら非同期を走らせられればいい。それにうってつけのクラスがSemaphoreSlim。「リソースまたはリソースのプールに同時にアクセスできるスレッドの数を制限する Semaphore の軽量版です。SemaphoreSlim は、Windows カーネルのセマフォを使用しない、軽量セマフォ クラスを提供します。」。です。.NET 4.0からの登場。使うメソッドはWaitAsync(これは.NET 4.5から)とReleaseがほとんどかな。.NET 4.0の場合はWaitAsyncのかわりにWaitで。

内部にCountを持っていて、それをWaitAsyncで減らし、Releaseで増やします。Countが0に達すると、WaitAsyncは待機するようになります。これを用いてForEachAsyncを作ってみると?

public static class EnumerableExtensions
{
    public static async Task ForEachAsync<T>(this IEnumerable<T> source, Func<T, Task> action, int concurrency, CancellationToken cancellationToken = default(CancellationToken), bool configureAwait = false)
    {
        if (source == null) throw new ArgumentNullException("source");
        if (action == null) throw new ArgumentNullException("action");
        if (concurrency <= 0) throw new ArgumentOutOfRangeException("concurrencyは1以上の必要があります");
 
        using (var semaphore = new SemaphoreSlim(initialCount: concurrency, maxCount: concurrency))
        {
            var exceptionCount = 0;
            var tasks = new List<Task>();
 
            foreach (var item in source)
            {
                if (exceptionCount > 0) break;
                cancellationToken.ThrowIfCancellationRequested();
 
                await semaphore.WaitAsync(cancellationToken).ConfigureAwait(configureAwait);
                var task = action(item).ContinueWith(t =>
                {
                    semaphore.Release();
 
                    if (t.IsFaulted)
                    {
                        Interlocked.Increment(ref exceptionCount);
                        throw t.Exception;
                    }
                });
                tasks.Add(task);
            }
 
            await Task.WhenAll(tasks.ToArray()).ConfigureAwait(configureAwait);
        }
    }
}

ほむ、わからん。ExceptionとかCancellationTokenとかでゴチャついてますが、よーわ、実行開始しようとするとWaitAsyncでカウントを減らして、実行完了したらReleaseでカウントを増やす。初期値の指定がそのまま並列実行数になる、って感じ。利用例を見ると

var httpClient = new HttpClient();
await Enumerable.Range(1, 100000)
    .ForEachAsync(async x =>
    {
        var str = await httpClient.GetStringAsync("http://hogehoge?q=" + x);
        Console.WriteLine(str);
    }, concurrency: 200);

実に簡単にひどぅーきなForEachができました。これは、Taskの実行開始はシーケンシャルです。これも何気に有難かったりしますねえ。実行完了のほうは順不同です。まあ、そりゃそうだ、って話ですね。

まとめ

SemaphoreSlimかわいい。

RespClient - PowerShell向けのRedisクライアント/コマンドレット

というものを作りました。

例によってインストールはNuGetで。PowerShellのコマンドレットを含んでいるのでSystem.Management.Automationがないと動きません(多分、よく知らない)。

RESPって?

REdis Serialization Protocolです。RespClientは、何かのRedisClientのラッパーではなくて、自前でプロトコルを解釈してSocket経由で叩いてます。といっても、RESPは非常にシンプルなプロトコロでして、そんなに難しくはありません。作ろうと思った発端は、プロトコルの定義を見てて、先頭の識別子がEnumで

public enum RespType : byte
{
    SimpleStrings = (byte)'+',
    Erorrs = (byte)'-',
    Integers = (byte)':',
    BulkStrings = (byte)'$',
    Arrays = (byte)'*'
}

こんな風に定義できて面白いなー、という、それがきっかけなのでした。ただ、せめて実用的なものを作りたいと思ったので、特にPowerShellに強くフォーカスするようにしています。

既存のクライアント、私が作ってるCloudStructuresなり、その元のBookSleeveなり、ServiceStack.Redisというのは、やはりC#から使うのが前提で、結構ヘヴィーだと思うのです、PowerShell的なコマンドラインインターフェイスで使うには。なので、Redis-Cli的な感覚で使える、Windowsネイティブのクライアントは、隙間産業的に、ちょうどなかったので良いかな、と。なので私にしては珍しくというか初めてコマンドレット作りましたし!

PowerShellコマンドレット

こんなふーに使います。

# モジュールはdllで提供されています。
Import-Module RespClient.dll
 
# RedisServerへのコネクションは、一度コネクションを張るとセッション中、ずっと維持されます。
# 他のパラメータは -Host, -Port, -Timeout があります。
Connect-RedisServer 127.0.0.1
 
# コマンドを送るのはSend-RedisCommandで。戻り値はUTF8Stringでデコードされます。
Send-RedisCommand "set test abcde"
 
# パイプラインモードもサポートしています。
Begin-RedisPipeline
Send-RedisCommand "set test fghijk"
Send-RedisCommand "incr testb"
Send-RedisCommand "incr testc"
Send-RedisCommand "get test"
Execute-RedisPipeline
 
# 明示的にコネクションを切るときはDisconnectしてください。
Disconnect-RedisServer

RespClient(.NET)

生の.NETクライアントのほうが、よりコマンドレット経由よりも高機能です。場合によってはPowerShellで使う場合も、コマンドレットではなくて、こちらを使ったほうがいいこともあるかもしれません。具体的な差は、バイナリセーフな値を投げることができるのと、戻り値のバイナリのデコード形式を自由に選べます。

using (var client = new Redis.Protocol.RespClient())
{
    // string command
    client.SendCommand("set a 1", Encoding.UTF8.GetString);
 
    // binary safe command
    client.SendCommand("set", new[] { Encoding.UTF8.GetBytes("test"), Encoding.UTF8.GetBytes("abcde") }, Encoding.UTF8.GetString);
 
    // use pipeline
    var results = client.UsePipeline()
        .QueueCommand("incr a")
        .QueueCommand("incrby b 10")
        .QueueCommand("get a", Encoding.UTF8.GetString)
        .Execute();
} // disconnect on dispose

まとめ

弊社でぎたぱそさんがたまに使ってます。(私は……そもそもあんま生で触る機会がなく、かな!?)

C#での動的なメソッド選択における定形高速化パターン

動的なメソッド選択、といってもなんのこっちゃというわけですが、身近な例だと、ようするにURLをルーティングして何のコントローラーの何のメソッドを呼ぶのか決めるって奴です、ASP.NET MVCとかの。ようするにLightNodeはいかにして速度を叩きだしているのか、のお話。自慢。嘘本当。

以前にExpression Treeのこね方・入門編 - 動的にデリゲートを生成してリフレクションを高速化という記事を書いたのですが(2011/04ですって!もうすっかり大昔!)、その実践編です。Real World メタプログラミング。

とあるController/Action

とあるControllerのとあるActionを呼び出したいとするじゃろ?あ、ちなみに別に例として手元にあるのがちょーどこれだったというだけで、別に同様なシチュエーション(動的にメソッドを選択する)では、うぇぶに限らず何にでも応用効きます。というわけでウェブ興味ね、という人も帰らないで!!!このサイトはC#のサイトですから!

// 何の変哲もない何か
public class HogeController
{
    public string HugaAction(int x, int y)
    {
        return (x + y).ToString();
    }
}
// コード上で静的に解決できるならこう書くに決まってるじゃろ?
var result = new HogeController().HugaAction(1, 10);

ただたんにnewして実行するならこうなります。が、これが動的にControllerやActionを選ばなきゃいけないシチュエーションではどうなりますん?ルーティング処理が済んで、呼び出すクラス名・メソッド名が確定できたというところから行きましょう。

// コントローラー名・アクション名が文字列で確定出来た場合
var controllerName = "ConsoleApplication.HogeController";
var actionName = "HugaAction";
 
var instance = Activator.CreateInstance(Type.GetType(controllerName));
var result = (string)Type.GetType(controllerName).GetMethod(actionName).Invoke(instance, new object[] { 1, 10 });

一番単純なやり方はこんなものでしょう。Activator.CreateInstanceでインスタンスを生成し、MethodInfoのInvokeを呼ぶことでメソッドを実行する。基本はこれ。何事も素直が一番良いですよ。おしまい。

動的コード生成事始め

あけましておめでとうございます。素直が一番、おしまい、で済まない昨今、リフレクションは実行速度がー、という亡霊の声が聞こえてくるのでshoganaiから対処しましょう。基本的にC#でリフレクションの速度を高める手段は一択です、デリゲート作ってキャッシュする。というわけでデリゲート作りましょう。

// ここから先のコードでは↓4つの変数は省略します
var controllerName = "ConsoleApplication.HogeController";
var actionName = "HugaAction";
var type = Type.GetType(controllerName);
var method = type.GetMethod(actionName);
 
// インスタンスが固定されちゃう
var instance = Activator.CreateInstance(type);
var methodDelegate = (Func<int, int, string>)Delegate.CreateDelegate(typeof(Func<int, int, string>), instance, method);

Delegate作ると言ったらDelegate.CreateDelegate。なのですが、静的メソッドならいいんですが、インスタンスメソッドだと、それも込み込みで作られちゃうので些かイケてない。今回は毎回インスタンスもnewしたいので、これはダメ。

が、Delegate.CreateDelegateの面白いところは、「オープンなインスタンス メソッド デリゲート (インスタンス メソッドの隠れた第 1 引数を明示的に指定するデリゲート) を作成することもできます」ことです。どういうことか、というと

// 第一引数にインスタンスの型が渡せる
var methodDelegate = (Func<HogeController, int, int, string>)Delegate.CreateDelegate(
    typeof(Func<HogeController, int, int, string>), method);
 
// だからこう呼べる(キャストしてたりしてActivator.CreateInstanceの意味がほげもげ)
var result = methodDelegate((HogeController)Activator.CreateInstance(type), 10, 20);

あら素敵。素敵ではあるのですが、面白いだけで今回では使い道はなさそうです、Activator.CreateInstance消せてないし、HogeControllerにキャストって、ほげほげですよ。

というわけで、ちょっと込み入った生成をしたい場合はExpressionTreeの出番です。new生成まで内包したものを、以下のように捏ね捏ねしましょう。

// インスタンスへのnewを行う部分まで生成する、つまり以下の様なラムダを作る
// (x, y) => new HogeController().HugaAction(x, y)
var x = Expression.Parameter(typeof(int), "x");
var y = Expression.Parameter(typeof(int), "y");
var lambda = Expression.Lambda<Func<int, int, string>>(
    Expression.Call( // .HugaAction(x, y)
        Expression.New(type), // new HogeController()
        method,
        x, y),
    x, y) // (x, y) => 
    .Compile();

ExpressionTreeの文法とかは3年前だか4年前だかのExpression Treeのこね方・入門編 - 動的にデリゲートを生成してリフレクションを高速化を参照してください、というわけでここでは解説はスルー。ExpressionTreeの登場はVS2008, .NET 3.5, C# 3.0から。もう5年以上前なのですねー。

さて、Compileは非常に重たい処理なので(カジュアルに呼びまくるようなコード例をたまに見ますが、マヂヤヴァイのでやめましょう)、作ったらキャッシュします。

// Compile済みのラムダ式はキャッシュするのが基本!
// 以下の様なものに詰めときゃあいいでしょう
// .NET 4以降はキャッシュ系はConcurrentDictionaryのGetOrAddに入れるだけで済んで超楽
var cache = new ConcurrentDictionary<Tuple<string, string>, Func<int, int, string>>();

ConcurrentDictionaryのKeyは場合によりけり。Typeの場合もあればStringの場合もあるし、今回のようなCotrollerName/ActionNameのペアの場合はTupleを使うと楽ちんです。Tupleは辞書のキーなどに使った場合、全値の一致を見てくれるので、適当に文字列連結して代用、なんかよりも正確で好ましい結果をもたらしてくれます。簡易的に作る場合は、私も、とても多用しています。

大文字小文字比較とかの比較まではやってくれないので、そこまでやりたければ、ちゃんとGetHashCode/Equalsを実装したクラスを作りましょう(LightNodeではそれらを実装したRequestPathというクラスを作っています)

そういえばPCLはWindows Phoneを対象に含めるとConcurrentDictionary使えなくてイラ壁なのでWindows Phoneは見なかったことにしてPCLの対象に含めないのが最善だと思います!どうせ端末出てないし!

: Delegate

ところでFunc<int, int, string>という型をLambdaに指定しているけれど、これもまた決め打ちで、動的じゃあない。んで、そもそもこの手の作る場合、メソッドの型って色々あるのね。

// メソッドの型は色々ある!
public class HogeController
{
    public string HugaAction(int x, int y)
    {
        return (x + y).ToString();
    }
 
    public double TakoAction(string s, float f)
    {
        return double.Parse(s) * f;
    }
}

こんな風にActionが増えたら、というか普通は増えるというか違うに決まってるだろという話なわけで、問題色々でてきちゃいます。 まずデリゲートの型が違うのでキャッシュ不可能。Func<int, int, string>のConcurrentDictionaryにFunc<string, float, double>は入りません。そして”HugaAction”や”TakoAction”という動的に来る文字列から、コンパイル時にデリゲートの型は決められない。ていうかそもそもパラメータのほうもxだのyだのって不明じゃないですかー?ゼロ引数かもしれないし10引数かもしれないし。

どーするか。型名指定ができないなら指定しなければいいじゃない。

// Expression.Lambdaに型名指定をやめ、CacheはDelegateを取ってみる
var cache = new ConcurrentDictionary<Tuple<string, string>, Delegate>();
var dynamicDelegate = cache.GetOrAdd(Tuple.Create(controllerName, actionName), _ =>
{
    // パラメータはMethodInfoから動的に作る
    var parameters = method.GetParameters().Select(x =>
            Expression.Parameter(x.ParameterType, x.Name))
        .ToArray();
 
    return Expression.Lambda(
            Expression.Call(Expression.New(type), method, parameters),
        parameters).Compile();
});

やった、大解決!

// 但しDelegateのキャッシュは呼び出しがDynamicInvokeでイミナイ。
// もちろん別に速くない。
var result = dynamicDelegate.DynamicInvoke(new object[] { 10, 20 });

はい、意味ありません。全然意味ないです。Funcなんちゃらの共通型としてDelegateで統一しちゃうと、DynamicInvokeしか手がなくて、ほんとほげもげ!

Everything is object

Cacheに突っ込むためには、あらゆるメソッドシグネチャの共通項を作らなきゃあならない。でもDelegateじゃあダメ。じゃあどうするか、というと、objectですよ!なんでもobjectに詰めればいいんです!

// 解決策・最も汎用的なFuncの型を作ること
// オブジェクトの配列という引数を受け取り、オブジェクトを返す関数である
// Func<object[], object>
// これを作ることによりDynamicInvokeを回避可能!(Boxingはあるけどそこは諦める)
 
// このキャッシュならなんでも入りそうでしょう
var cache = new ConcurrentDictionary<string, Func<object[], object>>();

良さそうな感じ、というわけで、object[]の配列を受け取りobjectを返すデリゲートを作っていきましょう。

// 作る形をイメージしよう、以下の様な形にするのを狙う
// (object[] args) => (object)new HogeController().HugaAction((int)object[0], (int)object[1])
 
// 引数はオブジェクトの配列
var args = Expression.Parameter(typeof(object[]), "args");
 
// メソッドに渡す引数はオブジェクト配列をインデクサでアクセス+キャスト => (cast)args[index]
var parameters = method.GetParameters()
    .Select((x, index) =>
        Expression.Convert(
            Expression.ArrayIndex(args, Expression.Constant(index)),
        x.ParameterType))
    .ToArray();
 
// あとは本体作るだけ、但し戻り値にもobjectでキャストを忘れず
var lambda = Expression.Lambda<Func<object[], object>>(
    Expression.Convert(
        Expression.Call(Expression.New(type), method, parameters),
        typeof(object)),
    args).Compile();
 
// これでふつーのInvokeで呼び出せるように!
var result = lambda.Invoke(new object[] { 1, 10 });

これは完璧!若干Boxingが気になりますが、そこは贅沢は敵ってものです。というか、実際はパラメータ作る前工程の都合でobjectになってたりするので、そこのコストはかかってないと考えて構わない話だったりします。ちなみにvoidなメソッドに関しては、戻り値だけちょっと弄った別デリゲートを作ればOKですことよろし。その辺の小さな分岐程度は管理するが吉。

Task & Task<T>

Extra Stage。今どきのフレームワークはTaskへの対応があるのが当たり前です。(いい加減ASP.NET MVCもフィルターのTask対応してください、LightNodeのフィルターは当然対応してるよ!)。というわけでTaskへの対応も考えていきましょう。

TaskだってobjectなのだからobjectでOK!ではないです。Taskなメソッドに求めることって、awaitすることなのです。Taskで受け取って、awaitするまでが、戻り値を取り出す工程に含まれる。

var result = new Hoge().HugaAsync();
await result; // ここで実行完了される、的なイメージ

といってもTaskは簡単です、ようは戻り値をTaskに変えればいいだけで、↑のと変わらないです。Func<object[], Task>ということです。Taskに変えるといっても、元から戻り値がTaskのもののデリゲートを作るわけですから、ようするところObjectへのConvertを省くだけ。簡単。

// もちろん、実際にはキャッシュしてね
var lambda = Expression.Lambda<Func<object[], Task>>(
        Expression.Call(Expression.New(type), method, parameters),
    args).Compile();
 
var task = lambda.Invoke(new object[] { 1, 10 }); // 戻り値はTask
await task; // 待機

そう、Taskはいいんです、Taskは。簡単です。でもTask<T>が問題。というかTが。Tってなんだよ、という話になる。例によってTは静的に決まらないのですねえ……。こいつはストレートには解決できま、せん。

少しだけ周りっくどい道を通ります。まず、メソッド呼び出しのためのデリゲートはTaskと共通にします。Task<T>はTaskでもあるから、これはそのままで大丈夫。で、取り出せるTaskから、更にTを取り出します。どういうこっちゃ?というと、.Resultですよ、Result!

Task task = new Hoge().HugaAsync(); // Taskとして受け取る
await task; // ここで実行完了される
var result = ((Task<T>)task).Result; // ↑で実行完了しているので、Resultで取り出せる

こういう風なコードが作れればOK。イメージつきました?

// (Task task) => (object)((Task<>).Result)
// キャッシュする際は、キーはTypeでTを取って、ValueにFunc<Task, object>が省エネ
var taskParameter = Expression.Parameter(typeof(Task), "task");
var extractor = Expression.Lambda<Func<Task, object>>(
    Expression.Convert(
        Expression.Property(
            Expression.Convert(taskParameter, method.ReturnType), // method.ReturnType = Task<T>
            "Result"),
        typeof(object)),
    taskParameter).Compile();
 
// これで以下のように値が取れる!
await task;
var result = extractor(task);

ここまでやれば、非同期にも対応した、モダンな俺々フレームワーク基盤が作れるってものです。

C# is not LightWeight in Meta Programming

そんなわけでこれらの手法は、特にOwinで俺々フレームワークを作る時などは覚えておくと良いかもです。そして、定形高速化パターンと書いたように、この手法は別に全然珍しくなくて、実のところASP.NET MVCやASP.NET Web APIの中身はこれやってます。ほとんど全くこのとーりです。(べ、べつに中身見る前からコード書いてたしその後に答え合わせしただけなんだから!!!)。まぁ、このぐらいやるのが最低水準ってことですね。

で、しかし、簡単ではありません。ExpressionTreeの取り扱いとか、ただのパターンなので慣れてしまえばそう難しくもないのですけれど、しかし、簡単とはいいません。この辺がね、C#ってメタプログラミングにおいてLightWeightじゃないよねっ、ていう事実。最初の例のようにActivator.CreateInstanceで済ませたり、dynamicだけで済む範囲ならそうでもないんですが、そこを超えてやろうとするとねーっ、ていう。

ExpressionTreeの登場のお陰で、今どきだとIL弄りの必要はほぼほぼなく(とはいえゼロじゃあないですけどね)、楽になったとはいえ、もっと、もっとじゃもん、と思わないこともない。そこでRoslynなら文字列でソースコードベタベタ書いてEvalでDelegateが生成できて楽ちんぽん!な未来は間違いなくありそうです。ですがまぁ、あと1~2年であったり、あとPortable Class Libraryに落ちてくるのはいつかな?とかっていった事情を鑑みる、まだまだExpressionTree弄りスキルの重要性は落ちなさそうなので、学ぶならイマノウチ!損はしません!

メタプログラミング.NETは、満遍なく手法が紹介された良書だと思いますので、読んだことない人は是非是非読むといいかと思います。参考資料へのリンクが充実してたりする(ILのOpCodeとか)のも嬉しい点でした。

まとめ

ExpresionTreeは優秀なIL Builder(Code as Dataとしての側面は残念ながらあまり活用できそうにもないですが、こちらの側面で活躍しているのでいいじゃないですか、いいの、か???)なんでも生成頑張ればいいってものではなく頻度によりけり。頻度少なけりゃDynamicで全然OK。ん……?

ちゃぶ台返しますと、ウェブのリクエスト処理的な、リクエストの度に1回しか呼ばれない場合だと、別にここがDynamicInvokeで実行されるかどーかなんて、ハイパー誤差範囲なんですねぇ、実は!クライアントアプリとか、O/R Mapperとかシリアライザとか、テンプレートエンジンのプロパティ評価とか、凄く呼び出されまくるとかじゃなければ、割とどうでもいい範囲になってしまいます。0.01msが0.001msになって10倍高速!なのは事実ですが、そもそもウェブは1回叩いて10msかかったりするわけで誤差範囲としか言い様がない次元になってしまう。

でも、ちゃんと頑張ったほうが格好はつくので、頑張ってみると良いです、みんなもやってるし!こんなのチキンレースですから。LightNodeの速さの秘訣はこれだけ、ではないですが、大事な一端には変わりないです。

C# ASP.NETのRESTフレームワークパフォーマンス比較大全

LightNode(という私の作ってるOwinで動くMicro REST Framework)の0.2出しました。でも皆さんあんま興味ないと思うので(!)、先にベンチマークの話をしましょふ。0.1を出した時にもグラフを出したのですが、よく見るまでもなく詐欺グラフで非常に良くなかったので載せ直し&NancyとかWCF RESTとか他のフレームワークも追加しました。そして今回測りなおしてみると、そもそも前回のものは致命的に計測のための環境作りにミスッていたので、まるっきりナシでした、すびばせん。

パフォーマンステスト

各ソースコードはLightNode/Performanceに置いてあるので再現できます。数字はrequest per secondで、Apache Benchで叩いているだけです。実行環境は「Windows 8.1/CPU Core i7-3770K(3.5GHz)/Memory 32GB」という、私の開発環境のデスクトップPC上で動かしてます。ホスト先はIIS ExpressじゃなくてローカルIIS。

オレンジと緑はふつーのIISでホストしてるもの。グレーはちょっと別枠ということで色分けてます。

結果に納得いきます?イメージとちょっと違う結果に?まず、WCFのRestが一番遅いです。これはどうでもいいですはい。次点がASP.NET MVC、そこからちょっと僅差でASP.NET Web API。これはJSONシリアライザの問題もあるかなってところ(MVCのデフォルトシリアライザはJavaScriptSerializerで、あれは遅い)かどうかは知りませんけれど、ともかくWeb APIは言われるほど遅い遅いなんてことはない、ってとこでしょーか。安心して使っていきましょう。そこから別に大きく差がついてってことなくNancy、ちゃんと定評通りにLightweightで速そうです、偉い。ServiceStackは、一応謳い文句どおりちゃんとFastestでした。

生OwinHandler(Async)とHttpTaskAsyncHandlerはほとんど変わらない。といったところから、Owin(Katana)でラップされることは、そんなにパフォーマンスロスは発生しない、と考えられるでしょう。この事実はかなり安心できて嬉しい。で、LightNodeがそれらよりもグッと高速。そう、LightNodeはちゃんと最速フレームワークなのです。で、しかし生OwinHandler(Sync)と差がちょっと付いちゃってるので、ここはもう少し縮められないかなあ、といったところ(実際のところ、LightNodeのデフォルトは出力をバッファするようになってて、そのオプションをオフにすればもっと迫れますが、実用的にはオンにせざるを得ないので、ここはオン時で)。そして最速はSyncのHttpHandler。生ハンドラ強い。

SelfHostとHeliosはちょっと別枠。SelfHostは一節によるとSloooooooooowって話もあったのですけれど、手元でやると普通に速いのですよねえ。そのSloooowってコード見たら、なんかそもそも他のテストと出力物が全然違ったので、その人のミスなのかなぁ?って思ってますがどうなのでしょうね。ともあれ、私の環境でやってみた限りではSelfHostはかなり速いです。

Helios(Microsoft.Owin.Host.IIS)は、System.Webを通さずにIISネイティブをペシペシ叩くことで超最速を引き出すとかいう代物で、まだ0.1.2-preでプロダクション環境に使える代物ではないとはいえ、とにかく速い。すぎょい。これだけ違うとニヨニヨしちゃうねぇ。

Sync vs Async

ベンチ結果の突っ込みどころは二点ほどあるかな、と。ひとつはHttpHandler(Async)がHttpHandler(Sync)に比べて険しく遅いこと。もう一つは、RawOwinHandler(ASync)とRawOwinHandler(Sync)ってなんだよハゲ、と。RawOwinHandlerはこんなコードになっています。

app.Run(context =>
{
    // 中略
 
    if (context.Request.Query.Get("sync") == "true")
    {
        context.Response.Body.Write(enc, 0, enc.Length);
        return EmptyTask; // Task.FromResult<object>(null)
    }
    else
    {
        return context.Response.Body.WriteAsync(enc, 0, enc.Length);
    }
}

違いはWriteしてるかWriteAsyncしてるか。で、これだけで1000rpsも変わってしまうんですねえ、お、おぅ……。HttpHandler(Async)とHttpHandler(Sync)も同じ話です。これねえ、困った話です。しかも、Heliosだと遅くならなかったりするので、原因はSystem.Webのネットワークストリームに対するWriteAsyncに何らかの欠陥があるのかなあ、と。それ以外にとりあえず考えつくのは、そもそもレスポンスサイズが小さいとかlocalhost同士だから、とかなくもないので(所詮マイクロベンチですから)、厳密にどーこうというのは言いづらくてまだ要調査ってところ。でもHeliosだと遅くならなかったりするのでもうHeliosでいいよ(投げやり)

まぁネットワーク離したりサイズ大きくしたりとかは、そのうちやりませうか。そのうち。多分やらない(面倒くさいの!)

ベンチ実行環境の注意

Windows Defenderのリアルタイム保護が有効ならば、無効にしましょう。これ、有効か無効かでめちゃくちゃ結果変わります。3000rpsぐらい変わるしHeliosにしても別に大して差が出ないとか、もう根源的に結果が変わります。Defenderがオンの時に測った結果とかクソの役にも立たないゴミデータなので投げ捨てましょう。ネットにある計測しました、とかってのもそれの可能性があるので見なかったことにしましょう。ファイアウォールとかもとりあえず切っといたほうがいいんじゃないでしょーか。

IISかIIS Expressかは、傾向としてそこまで大きく違うってこともないですけれど、IISのほうが成績は良好なので、一応測るのだったらIISでやったほうが良いかと思います。以前にやってた時は横着してIIS Expressでやってたのですけれど、反省ということで。そこまでやるならWindows Serverでー、とか無限に要求は加速しますが、フレームワーク同士の相対的な比較なので、そこまでやる必要はないかな?

LightNode 0.2

ここからタイトル詐欺の抱合せ商法。じゃなくて、最初はこっち本題で書いてたんですが思ったよりパフォーマンス比較が厚くなったので上に持ってきただけなんですよ……。ということでLightNode 0.2出しました。LightNode自体は0.1の解説LightNode - Owinで構築するMicro RPC/REST Frameworkを読んで欲しいのですけれど、0.1はOwinへの理解度が足りなかった成果、安定性に難があったり、細部が詰められてなかったりしました。けれど、今回はかなり完成度上がってます。このバージョンからは普通に投下しちゃって問題ないレベルに達しているかな、と。ベンチマークを細かく取って検証しているとおり、パフォーマンスについてもよりシビアに詰めています。

変更点は割といっぱいあります。

Enumバインディングの高速化
Enumパースの厳密化
T4クライアントコード生成内容の変更、アセンブリロック回避
ContentFormatterのコンストラクタ修正
IContentFormatterにEncodingインターフェイス
Extensionを|区切りで受け付けるように
void/Task時は204を返す
ReturnStatusCodeExceptionを投げることで任意のステータスコードで返せる
Optionでstringはデフォルトではnull非許可に
IgnoreOperationAttribute追加
フィルター追加

こんなとこで。あとGitHub Pages -LightNode立てたのでトップページがちょっとオサレに。

Enum高速化・判定厳密化

C#のEnum自体は凄く好きなんですよ、プリミティブとほとんど変わらない、という、それがいい。Javaみたいにゴテゴテついてると逆に使い勝手悪かったりパフォーマンス上の問題で使われない(Android!)とかって羽目になってたりしますし、これはこれで良いかな、って思ってます。拡張メソッドによってちょっとしたメソッドは足すことが可能になりましたしね。ただ、静的メソッドが頂けない。リフレクションの塊なので速くない、なんか色々使い勝手悪い、などなどビミョー感半端ない。

しょうがないので、LightNodeではEnum専用のインフラ層を構築して回避しました。高速化しただけじゃなくて、値の判定を厳密化しています。Enumの値が1,10,100の時に5を突っ込んだらダメ、って感じです。更にビットフラグに対しても厳密な判定がされるように加えているので([Flags]属性がついてるかどうかを見ます)、1,2,4,8の時に100が来たら死亡、7ならOK、って感じですにぇ。ASP.NET MVCなどでも、Enumでゆるふわな値が渡ってくるのはかなり嫌だったので、良いんじゃないかと思います。他、デフォルトでstringもnull非許可、配列の場合は空配列になるので、デフォではnullや範囲外の値というのは完全排除しています。

なお、このEnumインフラストラクチャは、後日、もう少し機能を追加して専用ライブラリとして切り出そうと思っています。使い道はかなり多いんじゃないかなー、と思いますのでお楽しみに。

例外によるステータスコード変更

ASP.NET Web API 2 で追加された機能について見てて、いいですよねー、ということで。実際、戻り値の型をシンプルなオブジェクトで指定した場合って、例外でグローバルに吹っ飛ばすしか手段ないですしね。

public class Hoge : LightNodeContract
{
    public int HugaHuga()
    {
        throw new ReturnStatusCodeException(HttpStatusCode.NotImplemented);
    }
}

単純明快でいいと思います。IHttpActionResultなんて作りゃあそりゃ最大の柔軟性ですがResponseType属性とかは、まぁ、やっぱあんまりだな、って思いますよ、ほんと……。

さて、実際のとこWeb APIはやっぱり一番参考にしていて、機能眺めながら、どうするか考えてます。ルーティングや認証は他のMiddlewareがやればいい、Request Batchingは入れたい、ODataはOData自体がイラネ、フィルターオーバライドはうーん?とか。色々。色々。

Middleware vs Filter

そして、フィルター入れました。最初から当然入れる気ではあったのですが実装時間的に0.1では間に合わずで。まぁ、あと、0.1の時点ではミドルウェアパイプラインとフィルターパイプラインの違いを言語化出来なかったり、実装方法というかインターフェイスの提供方法についても全然考えが固まっていなかったので、無理ではあった。今はそれらはしっかり固まってます。

というわけで、こんなインターフェイス。

public class SampleFilterAttribute : LightNodeFilterAttribute
{
    public override async Task Invoke(OperationContext operationContext, Func<Task> next)
    {
        try
        {
            // OnBeforeAction
 
            await next(); // next filter or operation handler
 
            // OnAfterAction
        }
        catch
        {
            // OnExeception
        }
        finally
        {
            // OnFinally
        }
    }
}

ASP.NET MVCとかの提供するOnActionExecuting/Executedとか、アレ、私は「大嫌い」でした。挙動が不明だから。Resultに突っ込むと何が起こるの?Executingで投げた例外はExecutedに届くの?(届かない)、などなど、分かりやすいとは言い難くて、LightNodeではその方式は採用したくなかった。

かわりにシンプルなパイプラインを採用しています。OWINのInvokeパイプラインとほぼ同等の。

// app.Use
Func<IOwinContext, Func<Task>, Task> handler
// LightNode Filter
Func<OperationContext, Func<Task>, Task> invoke
// インターフェイスでは
public abstract Task Invoke(OperationContext operationContext, Func<Task> next);

というか一緒です。見た目もやってることも一緒なミドルウェアとフィルターですが、違いは当然幾つかあります。第一に、ミドルウェアだとほとんどグローバルに適用されますが、フィルターはグローバル・クラス・メソッド単位の3つが選べます。そして、最大の違いは実行コンテキストを知っていること。フィルターが実行されるのはパラメータバインディングの後なので、実行されるメソッドが何かを知っています。どのAttributeが適用されているかを知っています。ここが、大きな違い。

フィルタはGlobal/Contract/Operation単位で設定可能ですが、順番は全て設定されたOrderに従います。AuthenticationとかActionとかの順序パイプラインはなし。だって、あの細かい分け方、必要です?認証先にしたけりゃOrderを-int.MaxValueにでもすりゃあいいんです。というわけで、そのへんは取っ払って、Orderだけ、ただしOrderのレンジは-int.MaxValueからint.MaxValueまで、かつデフォルトはint.MaxValue(一番最後に発火される)。です。ASP.NET MVCのデフォが-1で最優先されるってOrderも意味不明で好きじゃないんですよねえ……。

OperationContextはIsAttributeDefinedなど、属性のチェックや取り出しが容易になるメソッドが幾つか用意されてますにゃ。もし実行をキャンセルしたければ、nextを呼ばなければいい。その上で大きく結果を変えたければ、Owin Environmentを弄れば好きなようにStatusCodeでもResponseでもなんでも設定できます。十分十二分。

まとめ

パフォーマンスは、まぁ自分で測らないと納得しにゃいところもきっとあると思いますので自分で測るのが一番いーですね、そりゃそうかそりゃそうだ。生ハンドラに比べるとロスは少なくないなぁというのは現実かもしれませんねぇ(LightNodeは除く)。Owinを被せることのロスは少なめってのが確認できたのは安心できて良いですね、さぁ積極的に使っていきましょう。

LightNodeは超簡単。超速い。十分なカスタマイズ性。というわけで、真面目に実用的です!使うべき!さぁ今すぐ!あと、意図的に既存のフレームワークの常識と崩しているところも含めて、全ての挙動の裏には多くの考えが含まれています。全ての挙動は明確に選択しています。そーいうのも読み取ってもらえると嬉しいですね。

また、ちょっとした縁があって2014/2/8に北海道のCLR/Hにてセッションを一つ持ちます。北海道!あんまり東京から出ない私なのですが、こないだは大阪に行きましたし、ちょっとだけたまにはお外にも出ていこうかな、なんて思っていなくもないです。北陸とかにもそのうち行きたいですね、色々なのと重ならなければ……。

セッションタイトルは「LightNode Demystified - How to Make Extreme Fast Owin Framework」を予定しています。ネタは色々あるんですが、LINQはこないだ大阪でやったし、Real World Hyper Performance ASP.NET Architecture(Internal謎社)は、もう少し後でいいかなぁ(?)だし、前2つがうぇぶけー(Windows Azure 最新アップデート/最新Web アプリケーションパターンと .NET)なので、合わせて見るのが良いかにゃ、と。

LightNode Demystified、ですけれど、LightNodeを作ることを通してOWINとはどのようなものなか、どのような未来が開けるのか、というのを伝えられれば良いと思っています。私自身、実際に作ることによる発見がかなり多かったし、作ってみないと分からないことというのはかなり多いと思いますので、そうして開けた視野をシェアできれば嬉しいですね。

また、1/17には弊社で開催するめとべや東京#3にてLTでデモをするので、そちらのほうも都合がつく方は是非是非どうぞ。

OWINのパイプラインとMiddleware作成ガイド

あけおめました。振り返る~系の記事はこっ恥ずかしいのでいつまでも先頭に出ていると嫌なので、割と流したくてshoganaiので、記事をでっち上げます。実際切実。記事あげてる場合じゃなくても、これはこれでsetsujitsuなので許してあげてほしいのね。

Node.jsでKoaというフレームワークが盛り上がっているらすぃ。で、新しいWebフレームワーク Koa についてを見てて、あー、まんまKatana - Microsoft.Owinで置き換えられるなぁと思ったので、書いてみました。

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        // KoaとOwinを比較して
        // http://blog.kazupon.jp/post/71041135220/koa
 
        // 3. Response Middleware
        app.Use(async (context, next) =>
        {
            Console.WriteLine(">> one");
            await next();
            Console.WriteLine("<< one");
        });
 
        app.Use(async (context, next) =>
        {
            Console.WriteLine(">> two");
            await context.Response.WriteAsync("two");
            await next();
            Console.WriteLine("<< two");
        });
 
        app.Use(async (context, next) =>
        {
            Console.WriteLine(">> three");
            await next();
            Console.WriteLine("<< three");
        });
    }
}

比較すると、そっくりそのまま。Koaはフレームワークというか小さなツールキット、Connect/Koa = Katana, Express = ASP.NET MVC/Web API、みたいな図式で捉えればいいのでしょうね。

OWINの成り立ちについては、OWIN - Open Web Interface for .NET を使うで解説されていますが、Ruby- Rack/Python - WSGI/Perl- PSGIと同じようなものと捉えられます。OWINとKatanaに関しては、PerlのPSGIとPlackの関係性を見れば、そのまま当てはめることが可能です。

よって、OWINの基本的なことは、OWIN関連を漁るよりも、Plack Handbookを読んだほうがピッと理解できそうです。GitHubのリポジトリには日本語の生Markdown原稿もあるので、目を通しておくと、理解がとっても進みます。

Pluggable Pipe Dream

OWINがASP.NETにもたらしたものは2つ。一つはバックエンドの自由、IISでもSelfHostでもー、という側面。もう一つはプラガブルなMiddleware。そしてこれは、パイプラインになっているのですね、こちらのほうが開発者にとって注目に値する、影響力の大きなものです。

最初の例で書いた app.Use(async (context, next) => はMicrosoft.Owinによるもので、ラムダ式でその場でMiddlewareを定義していることに等しい(AnonymousMiddleware!)わけですが、まずはこっちから書いてったほうが、わかりやすいかな、と。(ちなみにRunはnextのないバージョン、つまりMiddlewareの終点)

app.Use(async (context, next) =>
{
    try
    {
        // 実行前の処理が書ける
 
        await next(); // 次のミドルウェアの実行(これを呼ばないことでパイプラインのキャンセルも可能)
 
        // 正常実行後の処理が書ける
    }
    catch
    {
        // 例外時の処理が書ける
    }
    finally
    {
        // 後処理が書ける
    }
});

こういったパイプラインは.NETには偏在しています。HttpClientのDelegatingHandler - HttpClient詳解、或いは非同期の落とし穴についてや、LINQ to Objects - An Internal of LINQ to Objectsの中身と変わらない話です、特にDelegatingHandlerは近いイメージ持ってもらうと良いかな、と。図にすればこんな感じ。

next()を呼び出すことで、円の中央、次のパイプラインに進む。きっと、一番最後、中心のMiddlewareはフレームワークとしての役割を担うでしょう(ResponseStreamに書いたりなど処理をかなり進めてしまうので、フレームワークが後続のMiddleware読んでも無意味というか逆に死んだりするので、フレームワーク部分では意図的にnext呼ばない方がいい←だから実際app.RunもNext呼ばないしね)、とはいえ構造上ではFrameworkとMiddlewareに別に区別はないです。処理が終わったら、今度は円の内側から外側に向かって処理が進んでいきます。Nextを呼ばなければ、途中で終了。1-2-3-4-5-4-3-2-1を、3で止めれば、1-2-3-2-1になる、といった感じです。これはASP.NET MVCのfilterでResultに小細工したり、Exceptionを投げたりして中断するようなものです。

HttpModuleだってHTTPパイプラインぢゃーん、というツッコミもあっていいですけれど、それよりもずっと単純明快な仕組みだというのがとても良いところ。こういった薄さであったり単純さであったり、をひっくるめたLightweightさって、とっても大事です。

Mapping

LightNode - Owinで構築するMicro RPC/REST FrameworkではURLは決め打ち!と言いましたが、むしろそもそも、URLのルーティングはLightNodeが面倒見るものではなくて、他のMiddlewareが面倒見るものなのです。例えば、APIのバージョン管理でv1とv2とで分けたい、というケースがあったとしましょう。その場合、MapWhen(Katanaに定義されてます)を使うと、条件指定で利用するMiddlewareのスタックを弄ることができます。

// Conditional Use
app.MapWhen(x => x.Request.Path.Value.StartsWith("/v1/"), ap =>
{
   // Trim Version Path
   ap.Use((context, next) =>
   {
        context.Request.Path = new Microsoft.Owin.PathString(
            Regex.Replace(context.Request.Path.Value, @"^/v[1-9]/", "/"));
        return next();
   });
 
    ap.UseLightNode(new LightNodeOptions(AcceptVerbs.Post, new JsonNetContentFormatter()),
        typeof(v1Contract).Assembly);
});
 
app.MapWhen(x => x.Request.Path.Value.StartsWith("/v2/"), ap =>
{
   // 手抜きなのでコピペ:)
   ap.Use((context, next) =>
   {
        context.Request.Path = new Microsoft.Owin.PathString(
            Regex.Replace(context.Request.Path.Value, @"^/v[1-9]/", "/"));
        return next();
   });
 
   ap.UseLightNode(new LightNodeOptions(AcceptVerbs.Post, new JsonNetContentFormatter()),
    typeof(v2Contract).Assembly);
});

LightNodeはサービスの記述された読み込むアセンブリを指定できるので、v1用アセンブリとv2用アセンブリを分けて貰って、Request.Pathを書き換えてもらえれば(/v1/部分の除去)動きます。これは単純な例ですが、複雑なルーティングだって頑張れば出来るでしょう。きっと。

OWINにはSuperscribeというグラフベースルーティング(何だそりゃ)とかもありますし、そういうのと組み合わせれば、実際LightNodeでうまく使えるかどうかは知りませんが、まぁ、そういうことです。やりたければ外側で好きにやればいいのです。プラガブル!

Headerが送信されるタイミング

話は突然変わって、Middleware実装上のお話。表題のことなのですけれど、原則的には「最初にWriteされた時」です。原則的には、ね。最初にFlushされた時かもしれないし、そもそもされないかもしれないこともあるかもですが、とはいえ原則的には最初にWriteされた時です。どーいうことか、というと

app.Run(async (context) =>
{
    try
    {
        // Writeしてから
        await context.Response.WriteAsync("hogehoge");
        context.Response.Body.Flush();
 
        // StatusCodeやHeaderを書き換えると
        context.Response.StatusCode = 404;
    }
    catch (Exception ex)
    {
        // 例外出る
        Debug.WriteLine(ex.ToString());
    }
});

これをMicrosoft.Owin.Host.SystemWebでホストすると、「HTTP ヘッダーの送信後は、サーバーで状態を設定できません。」というお馴染みのような例外を喰らいます。ちなみにHttpListenerによるSelfHostでは無反応という、裏側のホストするものによって挙動は若干違うのだけは注意。とはいえ、どちらも共通して、ヘッダーが送信された後には幾らStatusCodeやHeaderを書き換えても無意味です。上の例だと、404にならないで絶対200になっちゃうとか、そういう。

当たり前といえば当たり前なのですが、生OWIN、生Katanaだけで色々構築すると、Middlewareの順序によっては、そーなってしまうことも起きてしまいがちかもしれません。

なお、Katanaのソースコード読む時はHttpListenerのほうを中心に追ったほうが分かりやすいですね。System.Webのほうは、つなぎ込みがややこしかったり、すぐにブラックボックスに行っちゃったりで読みにくいので。若干の挙動の差異はあるとはいえ、概ね流れや処理は同じですから、まずは読みやすいほう見たほうが楽でしょう。

バッファリングミドルウェア

さて、そんな、Writeが前後して厄介というのを防ぐためのMiddlewareを作ってみましょう。解決策は単純で、上流のパイプラインでバッファリングしてやればいいわけです。

app.Use(async (context, next) =>
{
    var originalStream = context.Response.Body;
    using (var bufferStream = new MemoryStream())
    {
        context.Response.Body = bufferStream; // 差し替えて
        await next(); // 実行させて
        context.Response.Body = originalStream;  // 戻す
 
        if (context.Response.StatusCode != 204) // NoContents
        {
            context.Response.ContentLength = bufferStream.Length;
            bufferStream.Position = 0;
            await bufferStream.CopyToAsync(originalStream); // で、コピー
        }
    }
});

単純簡単ですね!そう、Middlewareとか別にあんまり構える必要はなくて、Global.asax.csに書いていたのと同じようなノリでちょろちょろっと書いてやればいいわけです。そして、それが膨らみ始めたり、汎用的に切り離せそうだったら、独立したMiddlewareのクラスを立ててやれば再利用可能。これはIHttpModuleを作るのと同じ話ですけれど、Middlewareは、それよりもずっとカジュアルに作れます。

さて、上のコード、しかしこれだとMemoryStreamが中で使ってる奴にCloseされちゃったりするとCopyToAsyncでコケてしまいます。いや、誰がCloseするんだ?という話はありますが、でも、例えばStreamWriterを使って、usingして囲んでStreamに書いたりすると、内包するStreamまでCloseされちゃうんですねぇ。

usingしないように注意する、というのも、パイプラインに続くMiddleware全てで保証なんて出来ないので、ここもまた上流で防いでやるのがいいでしょう。LightNodeではUnclosableStream.csというものでラップしています。どういうものかというと

internal class UnclosableStream : Stream
{
    readonly Stream baseStream;
 
    public UnclosableStream(Stream baseStream)
    {
        if (baseStream == null) throw new ArgumentNullException("baseStream");
 
        this.baseStream = baseStream;
    }
 
    // 以下ひたすらStreamを移譲
 
    // そしてCloseとDisposeは空白
 
    public override void Close()
    {
    }
 
    protected override void Dispose(bool disposing)
    {
    }
}

という単純なもの。これを、ついでに独立したMiddlewareにしてみますか、すると、

public class BufferingMiddleware : Microsoft.Owin.OwinMiddleware
{
    public BufferingMiddleware(OwinMiddleware next)
        : base(next)
    {
 
    }
 
    public override async Task Invoke(Microsoft.Owin.IOwinContext context)
    {
        var originalStream = context.Response.Body;
        using (var bufferStream = new MemoryStream())
        {
            context.Response.Body = new UnclosableStream(bufferStream); // Unclosableにラップする
            await this.Next.Invoke(context); // Microsoft.Owin.OwinMiddleware使うとthis.Nextが次のMiddleware
            context.Response.Body = originalStream;
 
            if (context.Response.StatusCode != 204)
            {
                context.Response.ContentLength = bufferStream.Length;
                bufferStream.Position = 0;
                await bufferStream.CopyToAsync(originalStream);
            }
        }
    }
}

フレームワークレベルのものを作る時は、このレベルまで気を使ってあげたほうが間違いなくいいかと思います。

Owin vs Microsoft.Owin

Middleware作るのにMicrosoft.Owin.OwinMiddlewareを実装する必要はありません、InvokeとTaskと、などなどといったシグネチャさえあってればOKです。同様にIOwinContextはKatanaで定義してあるものであり、Owin自体はIDictionary<string, object>が本体です。

Katana(Microsoft.Owin)は便利メソッドの集合体です。Dictionaryから文字列Keyで引っ張ってResponseStream取り出すより、context.Response.WriteAsyncと書けたほうが当然楽でしょふ。他にも、Cookieだったりヘッダだったり、Middlewareの定義用ベースクラスだったり、などの基本的な、基本的な面倒事を全てやってくれる薄いツールキットがKatanaです。冒頭の、Node.jsのKoaみたいなものであり、PerlのPlackに相当するようなもの、と捉えればいいんじゃないでしょーか。

LightNodeはMicrosoft.Owinを参照していません。これは、依存性を最小限に抑えたかったからです。その分だけ、面倒事もあるので、楽したかったり社内用Middlewareを少し作るぐらいだったら、Katana使っちゃっていいと思いますですね。リファレンス実装、でありますが、どうせ事実上の標準として収まるでしょうし。フレームワークレベルでがっつし作ってみたいという時に、依存するかしないか、どちらを選ぶかは、まぁお好みで。依存したって全然構わないし、依存しないようにするのもそれはそれでアリだと思いますし。

HTMLを書き換えるMiddlewareを作る

というわけで、応用編行くよー。mayuki先生の作られているCarteletというHTMLパーサー/フィルターライブラリがあるのですが(某謎社で使われているらしいですよ)、それをOwinに適用してみましょう。Carteletのできることは

HTMLのそれなりに高速でそれなりなパース 出力時にCSSセレクターで要素に対してマッチして属性や出力フィルター処理 フィルターしない部分は極力非破壊 ASP.NET MVCのViewEngine対応 CSSのstyle属性への展開 (Cartelet.StylesheetExpander)

だそうです。

例として、class=”center”という属性を、style=”text-align:center”に展開するというショッパイ決め打ちな例を作ってみます。こんなMiddlewareを作ります。

// Cartelet Filter Middleware
app.Use(async (context, next) =>
{
    var originalStream = context.Response.Body;
    using (var bufferStream = new MemoryStream())
    {
        context.Response.Body = bufferStream; // 差し替えて
        await next(); // 実行させて
        context.Response.Body = originalStream;  // 戻す
 
        if (context.Response.StatusCode != 204) // NoContents
        {
            // Carteletによるフィルタリングもげもげ
            var content = Encoding.UTF8.GetString(bufferStream.ToArray());
 
            var htmlFilter = new HtmlFilter();
            htmlFilter.AddHandler(".center", (ctx, nodeInfo) =>
            {
                nodeInfo.Attributes.Remove("class");
                nodeInfo.Attributes["style"] = "text-align:center";
                return true;
            });
 
            var node = HtmlParser.Parse(content);
 
            var sw = new StreamWriter(context.Response.Body); // usingしない、stream閉じないために(leaveOpenオプションもあるのでそちらのほうが望ましいけど横着した)
            var cartelet = new CarteletContext(content, sw);
 
            htmlFilter.Execute(cartelet, node);
 
            await sw.FlushAsync(); // usingしない時はFlushも忘れないように。。。
        }
    }
});

Carteletの受け取るのがStringなので、全パイプラインが完了するまではバッファリングします。で、それで手に入れたStringをCarteletに流し込んで、本来のBodyに流し込む。(Content-Lengthの設定を省いてるので直に流し込んでますが、設定が必要なら再再バッファリングががが、まぁ、どうせ更に上流でgzipとかするだろうから、ここでContent-Length入れる必要はあんまにゃいかな!)

実際に結果を見てみると、

// これを実行すると
app.Run(async context =>
{
    context.Response.StatusCode = 200;
    context.Response.ContentType = "text/html;charset=utf-8";
    await context.Response.WriteAsync(@"
            <html><body>
            <div class=""center"">
               ほげほげ!
            </div>
            </body></html>");
});
<html><body>
<div style="text-align:center">
   ほげほげ!
</div>
</body></html>

というHTMLが出力されます。へーへーへー。色々応用効きそうですね!

OWINはパイプラインの夢を見るか?

色々出来る、しかも色々簡単!素晴らしい素晴らしい!プラガブル!はたして本当に?実際、フレームワーク書いたりミドルウェア書いたりしてると、ふつふつふつと疑問が湧いてきます。そういう時は先行事例を見ればいい、というわけでPythonのWSGIでは以下の様な話が。

(翻訳) WSGIは死んだ: WSGI Liteバンザイ!

すべてがプラガブルで、モノリシックなアプリケーションフレームワークを持つ理由がもはや一つもないような未来を思い描いていました。すべてライブラリ、ミドルウェア、デコレータでまかなえるからです。 悲しいことに、そんな理想的な未来はやってきませんでした。

OWINでも、一個のでっかいフレームワークを持つ必要なんてない!と言える時が来るか、というと、さすがにそれはないでしょうねえー。また、たとえ分離可能なコンポーネントであっても、フレームワークの提供するシステム(フィルターやプラグイン)から離れられるかというと、必ずしもそうではないのかな、って。

Middlewareのパイプラインは、ASP.NET MVC/Web APIとかのフィルターのパイプラインとも同じようなものです。だったらフィルターで作るよりMiddlewareで作ったほうが、フレームワークという制限から離れられて良さそう。でも、Middlewareの欠点は、後続のパイプラインのコンテキストを知らないことです。認証を入れるにしても、[AllowAnonymous]属性が適用されているかなんてしらないから、全部適用するかしないか、ぐらいにしか出来ない。filterContext.ActionDescriptorのようなもの、というのは、フレームワークの内側のシステムしか持ち得ないのです。でも、そうしてフィルターとして実装すれば、フレームワークに深く依存することになる。

そんな悩ましさを抱えつつも、それは、あんま無理せずに、コンテキスト不要なら最大限独立性の高いOwin Middlewareとして。そうでないならアプリケーションのプラグイン(フィルター)として。でいいかな、って思ってます。今のところ。何れにせよIHttpModuleなんかよりは遥かに作りやすいし、その手の話だって今に始まったことじゃあないのよね?HttpModuleだってHTTPパイプラインぢゃーん、って。はい。

2014/1/18(土)に開催されるめとべや東京#3(開催場所は謎社です)では、LTで5分でサービスAPIをOwin/LightNodeを使って作って実際にAzure Web Sitesにホストするまで、デモしようと思ってますので、OWIN知りたい、どう動かすのか見てみたい、って人もどうぞ。めととは。

LightNode - Owinで構築するMicro RPC/REST Framework

LightNodeというMicro RPC/REST FrameworkをOwinで作りました。というわけで、LightNodeについて……の前に、そもそもOwinって何?という感じだと思いますので、作成物を通してOwinが開くC#によるウェブ開発の未来について、もしくはOne ASP.NETというヴィジョンが見せる世界についてお伝えしようかな、と。これはOne ASP.NET Advent Calendar 2013への記事ですしね!ちなみに副題は「OWINでハイパー俺々フレームワーク作成」。きゃうん。

LightNode

バージョンはまだ0.1です。急ぎで作ったので、そう完成度高くないです。とはいえ十分動きますし、これは来年育てていきたいと思っているフレームワークです。やる気は、かなりあります。半年後ぐらいには実用になってるかなあ、と。ソースコードとか課題管理はGitHubで。

例によってインストールはNuGetから。

細かいパッケージが実はいっぱいあったりして……。

LightNodeが提供するのはサーバーサイドフレームワーク(競合はASP.NET Web APIです)と、クライアントサイドのAPIアクセスコード自動生成(WCFがやっているような!)、両方です。クライアントサイドの生成は、Unity3Dへのコード生成が最初のターゲットだったはずなんですが時間的な都合上、今はPCLだけ、です。まあ近いうちにはUnityのは出します、あとTypeScript用のも。

目標はクライアントサイドからサーバーサイドまで全てC#で統一されることによる生産性の超拡張を具現化すること。クライアントがUnityでサーバーがOwinで全部C#、みたいな、ね。両方C#で作り上げられることによるメリットを最大限引き出すことを目指しています。また、JSONオンリーではなくMessagePackやProtocol Buffersでのやり取りも可能なように、パフォーマンスを最大限追求します。また、そのうえで他言語との通信も捨てない、というわけでHTTPでRESTなでほげもげは捨てず、他言語からもサーバーへは自由にアクセス可能です。

逆にRESTfulでビューティフォーなURL設計とかは優先度ゼロなので完全に捨てています。

Lightweight as a Server

LightNodeは超絶Lightweightなフレームワークです。何がLightweightかというと、パフォーマンスと実装の簡単さ、両方を指して言ってます。特に実装の手間はほとんどないぐらい非常に軽量です、ASP.NET Web APIとか超重量級ですからね(それはさすがにいいすぎ)。

サーバーはOwin上に構築されていますので、まずOwinMiddlewareのセットアップが必要です。コンフィグだけは少し書いて下さい。SelfHostでもIISでもいいので、どちらかのOwinホストパッケージをNuGetで引っ張ってきて、スタートアップクラスでUseLightNodeする。

// OwinのStartup
public class Startup
{
    public void Configuration(Owin.IAppBuilder app)
    {
        // 受けつけるVerbを決めたりデフォのTypeFormatter(複数も当然できる)設定したり
        app.UseLightNode(new LightNodeOptions(
            AcceptVerbs.Get | AcceptVerbs.Post, 
            new JavaScriptContentTypeFormatter()));
    }
}

準備はこれだけ。で、実際にAPIはどうやって作るかというと、LightNodeContractを継承したクラスのパブリックメソッドが、自動的にAPIとして公開されます。

// LightNodeContractを実装すると全てのpublicメソッドがAPIになる
// URLは {ClassName}/{MethodName} で固定
// この場合だと例えば http://localhost/My/Echo?x=test
public class My : LightNodeContract
{
    // 戻り値は↑で設定したContentTypeFormatterでシリアライズされて渡る    
    public string Echo(string x)
    {
        return x;
    }
 
    // 今時なのでasyncもサポートしてるよ!戻り値はvoid, T, Task, Task<T>が使えます、ようは全部。
    // パラメータのほうは配列、Nullable、オプション引数あたりはOK
    public Task<int> Sum(int x, int? y, int z = 1000)
    {
        return Task.Run(() => x + y.Value + z);
    }
}

これで、「http://localhost/My/Echo?str=hoge」で叩けるってことになります。URLは {ClassName}/{MethodName} の形式で完全に統一されて、カスタマイズの余地はありません。

サーバー側は基本的にこれだけです。単純!地味!

必要最小限のラインってどこかなぁ、というのを考えた時、ここになるかな、と。ルーティングやパラメータのバインディング、レスポンスへの戻り値の書き込みなどはフレームワークがやってくれなきゃ死ぬけれど、それ以上はない。これだけでも割と十分便利に使える、の限界ラインを狙って、極力、機能を削ぎ落とす形で取捨選択しています。ちょっと不便、なぐらいで存外良かったりするのですよ、ちょっと便利、のために色々なものが引っ張られるより100倍良いでしょう?

あと私は「設定より規約」って嫌いなんですよね。別にXML Hellがいいとは言わないですが、あのやり方はLL向けかなあ、という気が相当してまして、C#でそれをやっても嬉しいところってあんまないんじゃないかって思います。属性とか型をどういう活かすか、のほうがいいとオモイマス。

Lightweight as a Client

純粋(?)なRESTって、C#でも、他のどの言語でも、決して扱いやすいわけじゃない。だからラップしたHogeClientを作りますよね。そして、そうした特化したRestClientの作成って、結構難しい。使いやすいClientって中々作れるものじゃあないです。手間がかかるうえに使いにくいものが出来上がるなら、絶望的です。だからサーバーAPIとクライアント、自分たちで両方を作る時、もんのすごく苦労してしまう。どこもLightweightじゃない。こんなことならSOAPでVisual Studioで自動生成してくれてるののほうが100億倍Lightweightだったよー、とかね、それはそれで事実です。

そこでLightNodeは真のLightweightを提供します。自動生成するからコストゼロで完璧なClient SDKが手渡されます。

// 中身はHttpClientなので当然全部async
// メソッドは全て
// client.{ClassName}.{MethodName}Async({parameter}) で生成されます
 
var client = new LightNodeClient("http://localhost");
 
await client.Me.EchoAsync("test");
var sum = await client.Me.SumAsync(1, 10, 100);

C#クライアントにとって、自然な操作感でサーバーサイドへとアクセスし、戻り値を受け取ることが出来ます(複雑なオブジェクトは内部のシリアライザを通して自動変換されます)。クライアント側にとってはRPCのように、サーバーを意識せず透過的にやり取り可能なこと、を目指しました。

この自動生成コードは、HttpClientを使ったRestClientとしては、割とイイ感じに出力するので、そういったのの参考にもどうぞ多分。REST APIはこういった形にラップされてるのが使いやすいと思ってるんですね、私は。インターフェイスの明示的実装の活用例。手作業だと面倒でサボッてしまいがちなCancellationTokenも受け取り可能になってたり、その辺は機械生成ならではの徹底さです。

ちなみに現状は実装時間的都合でまだPOSTにしか対応してない(次のアップデートでGETにも対応させます……)。

Micro RPC/REST Framework

Micro RPC FrameworkないしMicro REST Frameworkというのは造語です。ググッてもさして検索結果には出てきません。とはいえ、言わんとすることは分かるのではないでしょうかしらん。ヘヴィ級ORMのEntity Frameworkに対する、機能最小限でコンパクトなDapper。みたいなものです。徹底的に削ぎ落としたREST Framework。対極にあるのはUltra Super HeavyなFramework、って何?というと、ASP.NET Web APIかな。そう、ASP.NET Web APIって、別にLightweightじゃないよね?と、ずっと思っていて。ずっとしっくりこなくて。

というか既存のRESTなフレームワークってどれもLightweightに思えない。何が自分の求めているものなのかなあってずっと考えていたのだけれど(その間、会う人会う人にWeb APIってしっくりこないんです!と吹っかけて回ってた、どうもご迷惑おかけしました)、RPCだ!って至りまして。一周回ってRPC、これはアリだ、と。

REST vs SOAP, REST vs RPC, REST vs WCF

そもそも対立軸がオカシイ。そして、その結果、orになるんだよね、どちらを選びますか?って。それ以外がないの。なんでそう極端な対立になってしまうの?でも、しかし、それはある意味正しい。だって何かを作るには、この世にあるものから選ぶしかないのだから。ヘヴィなSOAPが嫌ならRESTしかなく、ヘヴィなRPCが嫌ならRESTしかなく、ヘヴィなWCFが嫌ならREST(ASP.NET Web API)しかない。

でも、本来は選択肢もっとあって良かったはずなんだよね。どうして中間がなかったんだろうね。そんなにRESTfulは素晴らしく輝かしい未来だったのかな。あまりにも、SOAPが、WCFが辛すぎて反動で極端に振れるしかなかったのかな。

RESTful

どうでもいい。だからLightNodeはGETとPOSTしかありません。

XML/JSON/XXX-RPC

doudemoii。入/出力がフォーマットに固定されるのが世の中的に厳しい。XMLは今どきアリエナイといわれてもshoganai感じになってきてしまっているし、その他のバイナリ形式もJavaScriptで扱いにくくなったりして絶望感ある。JSON最強はありますけど、それはそれで、一部クライアントとはMsgPackとかProtobufとかで高速省スペースな通信したいって欲求には応えられない。仕様もあってないようなものだし、それらに従っていいこと、あまりない。

Language Interoperability

LightNodeはかなりC#に依存というか、むしろ尻尾から先頭までC#で一気通貫して通せることをメリットの一つとしています。とはいえ(広義の)RESTなので、HTTPでGETかPOSTでアドレス叩けば結果帰ってきます。他の言語からも叩けるって物凄く大事なので、いくら一気通貫、C#で大統一理論を正義にしていても、大事にしてあげたいです。JavaScript無視するとか自殺行為ですしね(TypeScriptコードの生成は将来的に作りたいものの一つです)。

仕様は、URLは{ClassName}/{MethodName}、パラメータはGETはクエリストリング、POSTはx-www-form-urlencodedで送ります。そのためということもあって、基本的にパラメータの型には制限があって、基本型(intとかstringとかDateTimeとか)のnullableとarray、それとオプション引数までにしか対応していません。複雑な型はダメ。

ダメな理由としては、あと、それ許可するとメソッドや引数がAPIドキュメントの代わりにならないんですよね。何を渡すことが許されるているのか、のシンプルさが消える。せっかくC#側で作ることの良さ、型があること、を消してしまうほうがmottainai、トレードオフとしてナシという判断です。そしてそのほうが言語間Interoperabilityにも有利ですし。

レスポンスのほうは自由です。何でもありです。基本的にbodyに書かれるだけなので、シリアライズ可能なものならなんでもOK。シリアライザも自由に選べます。こういった形式が自由なのは、パフォーマンスのためです。C#でガリガリに速くしたいなら、やっぱProtobufやMsgPackだろう、と(バイナリだから単純に高速省スペースとかいうのはただの幻想なのでWCFをそういう目では見ないようにしましょう)。でもJSONで吐けないのはそれはそれでありえないわけで、自由に選べる、かつ共存できるように(拡張子やContent-Typeで識別します)しています。

RPC風であり、REST風な中間点がこれかなあ、と。これなら俺々仕様っぽさは特になくRESTといって納得できるレベルに収まってるかと。そのうえで、クライアント側的にはRPC風に使えるのでシームレス感が相当ある。APIの構造がC#に引っ張られて、他言語からキモチワルイ感を醸しだしてしまう可能性はあるのですが(但しメソッド名のcamel,Pascalは自由でどちらでも通るようになってます)、こればっかりはshoganaiかなあ。

そもそもREST的な公開されてるほげもげって各言語、どの言語でも決して使いやすくはないような。だからSDKでラップしたものを使うでしょう?言語中立で万歳、みたいな理想世界がない以上は、プライマリの言語での使いやすさ+セカンダリ以降でも可能な限り使いやすさを維持できる構造、にするのがベターかなあ、って。思ってます。

Why Code Generation? Why not Dynamic Proxy?

今のクライアントコードは、T4によるソースコード生成になっています。正直ダサい。クライアント側はソースコード生成よりも、共通のインターフェイスに対して動的コード生成でProxy作ってやるほうが手軽に扱えていいのよね。どういうイメージかと言いますと、例えば

// こういうインターフェイスがサーバー側とクライアント側が共に参照するDLLに定義してあって
public interface IHoge
{
    int Sum(int x, int y);
}
 
// サーバー側は↑のインターフェイスを実装する
public class HogeContract : IHoge
{
    public int Sum(int x, int y)
    {
        return x + y;
    }
}
 
// クライアント側は↓のような形で使える
// Createの戻り値がIHogeになってて、その実装は動的生成されたもの、という感じ
var sum = LightNodeClient.Create<IHoge>("http://localhost").Sum(10, 20);

実にスッキリしていいですね!クライアントサイドのIHogeの実装は、動的コード生成により実行時に挿入されるので一切、手を加える必要はありません。ちなみに実装方法はAssemblyBuilderを使ってひたすらILゴリゴリです。ExpressionTreeのCompileToMethodは静的メソッドしか作れないので、↑のイメージのようなインスタンスメソッドへの生成は気合入れて書くしかないのですねえ、やれやれ……。

でも、今回はソースコード生成にしました。それはIL書くのが面倒だから、ではなくて(実際面倒だからってのはちょっとありますが!)、理由はそれなりに幾つかあります。

まず、インターフェイスの戻り値=クライアントにとっての戻り値、じゃあなくなってます。具体的にはTaskです。非同期以降の世界ではクライアント側の型はTask以外はありえないんです。ここで、じゃあインターフェイス側もTaskを強要すればいい、ってのは、それは不便なのでナシですしねえ。クライアント側のメソッド名はXxxAsyncにしたいとかってのもありますし、やっぱ、現代においてはインターフェイスをきっちり一致させるというのは難しい。

あと、Unity。まあ、何度か名前↑で出しているようにUnityはかなりターゲットなわけですが、UnityのC#ってバージョン古いのですよね、Taskなんてないんですよ……。そんなわけで各プラットフォーム毎に全然違う生成したほうがいいってことになってしまいますよねえ、と。C#以外にTypeScriptなんかもターゲットにしたいですしね。

そして最後に、AssemblyBuilderはフル.NET Frameworkにしかない。WinRTやPhone、当然PCLにはない。ないないないないなので、手間隙かけてIL書いてもあんま嬉しくなれない。

そんなわけで、ソースコード生成を手法に選んでいます。

とはいえ、提供手段がT4であることが良いかどうかはビミョイところですね。こういうの自体は、別に割とあるパターンではあるのですけど、例えばPetaPocoやORM LiteなどMicro ORM系はEFなどのヘヴィーなデザイナの代わりとしてT4を用いているし、 T4 MVCとかもあるし、……、うーん、そのぐらいか。あんまないね。

あと今の実装はdllをロードしてそれを解析するんですが、ロードしたあとそのまんまアセンブリ掴みっぱなしで解放されないから、解放するにはVS再起動しないといけないとかいうクソ仕様とかも残ってるので、何とかしなきゃ度は相当高いです。誰か解決策教えてください。

Performance

機能面では最小な上に(劣る、とは言いません)、わざわざ新しく作る以上、パフォーマンスで負けていたら馬鹿みたいな話です。というわけで結果。

OWIN上のWeb API、OWIN上のLightNode、OWIN上の生app.Run、あとふつーにIISでホストする生HttpHandlerの4つでテキトーに測ってみました。Nancyは加えようと思ったんですがちょっと動かなくて調べる時間がなかったので(この記事はAdvent Calendar的にギリギリで書き上げているのです!)いったんナシ。

んで、速いです。というかほっとんど生HttpHandlerと変わらない速度出せてます。そりゃ機能少ないんだから当たり前……、ではないです。機能が少ない=速い、に直接結びつくほど世の中、甘くはありません!この手のものを作るにあたって速度を稼ぐポイントは幾つかあって、しっかりポイント抑えたコード生成(&キャッシュ)をしつつ、余計な要素を足さないことで最速になります。そりゃそーだ。ともあれ、これ以上は速くならないという限界ラインを突いてます。これより先はどう頑張っても誤差範囲は超えないでしょう、というか生Handler近辺の時点で、もう大して変えられんです。

その辺の実装のコツのお話はまた次回にでも。(ただEnum周りのマッピング処理が現在ゴミなのでEnum入れると遅いです、これは次回までに改善します)

Owin

ASP.NET Web APIがOwin対応とか、そういうのどーでもいーんだよね。だってIISにホストするでしょ?SelfHostとか別になくてもいいレベルでしょ?プロダクション環境では使わないでしょ?というわけで、あるものを使うという点では、別に今はOwin対応とかドウデモイイレベルの話です。皆が今Owinにさして興味持てなかったり使い道に想像沸かないとしても、そりゃそうだ、です。だってIISでいいんですもの。

Owinの利点はMiddlewareを組み合わせられること。けれど現状は、多様なMiddlewareは、特にはない。できたてほやほやみたいなものだから。むしろASP.NET Web APIやASP.NET MVCレベルでのコンポーネントのほうがあるし、将来的にもきっとそうでしょう。つまり、Middlewareも利点だー!と声高に言ってもshoganaiところがある。

でも、それでも、そこに未来はある。Owinは誰もが簡単にMiddlewareを作れる。小さなちょっとしたユーティリティから、大きいフレームワークまで。ついに始まった自由の世界。多様なMiddlewareは、今は、特にはない。でも、作ればいい、必ず彩り豊かになる。そうなればASP.NET Web APIのOwin対応なども、意味がでてくる。

そしてパフォーマンスですら手に入る。ああ、パフォーマンスは大事だ、そう、本当は大事でなかったとしても、とにかくキャッチーだからね。今までのASP.NETコアランタイム、System.Webがヘヴィだとしたら、それを完全にバイパスして直繋ぎしたら。発表されたHelios IIS Owin Web Server Hostは驚異的なパフォーマンスを見せている。なるほど、すごく魅力的に見える。なにより、Microsoftは本気なんだなって気がする。Helios自体はまだαだけど、今はSystem.Webにホストしてもらって、Heliosが完成したらそっちでホストすればいい。そこが選べるのもOwinのいいところだ。ああ!素晴らしいじゃないか、Owin!

Create Your Own Framework

俺々フレームワークは悪。常識です。常識。かといって、何もかも作らないわけにはいきません。何を作り、作らないか、その見極めが戦略として非常に大事。自分の戦略でもそうだし会社だったらなお大事。

さて、今回は作ったわけですけれど、その理由は単純にないから。ないものは作る。当たり前だよにぇ。といっても何もかもを満たすものなんて存在しないので、妥協できるかどうかのラインを見定めるってことではあるのだけれど。妥協ラインですが、C#の場合って、Microsoftで完結するものなら凄く整ってるんですよね、妥協OKというかむしろ完璧すぎるぐらいに。でも、今回の需要はMicrosoftの外側、Unityとか他のクライアント系のとか、それらと一気通貫に繋がって欲しいって需要なのです。Microsoftの中で完結してそれ以外とは疎結合、じゃなくて、繋がれる範囲は可能な限り全開に密結合して欲しいってのがリクエスト。そういうのって、未来永劫Microsoftから出てくることはない。絶対に。だから、作るって結論になる。

あともう一つはどのぐらいのクオリティで作れるか。作ったはいいけどクソクオリティだったら不幸になるだけだからね!そして、C#の場合はVisual Studioとの統合具合もかなり大事。だから、MVCフレームワークなどだと、単純に作業量が超絶多くて全体のクオリティを保つのは非常に大変なうえに、ASP.NET MVCはVS統合が進んでてサクサクViewとControllerを相互に移動出来たりコンパイルエラーがくっついてたり、そういうところまで面倒見るのは不可能に近い。だから、部分的に良い物を作れたとしても全体的には超えるのって凄く難しいから、俺々フレームワークは、あまり良い選択肢にはなれなさそう(でもNancyとか頑張って欲しい!)。

Service系のフレームワークだとViewとかとの面倒みなくていいしVS統合もそんなに気を配らなくていい(WCFぐらいパーフェクトな統合があればそりゃ素敵だけど、WCFは統合されてはいても他に問題だらけなので除外)、最小限の機能のラインが見えていて、かなり満たしやすい。性能だって少し頑張れば既存のものを抜くのも簡単。そんなわけで作るのはアリだ、のラインに個人的には達しました。

Owin EcoSystem

Service系ならば、そもそもHTTPに乗らなくてもいいじゃない?特にパフォーマンス優先なら!という選択もありますね。それを選ばないのは、エコシステム。サーバー側には沢山のノウハウやシステムがあり、何もしなくても最高のInteroperabilityがある。通信関連ではHTTPったら最強ね。っていうのは揺るがない。よほどパフォーマンス優先な根幹的な何かを作るのでなければ。

そして、Owinもまた理由になります。今までの俺々フレームワークの最大の欠点は、全て自前で作るしかなかったことです。でもOwinがあれば違う。認証?他のMiddlewareで。パフォーマンスモニタ系?例えばGlimpseは最高のモニタライブラリだけど、俺々フレームワークで、こういうのが一切使えなくなるって、痛手というか、それだけでありえないレベルになりますよね。でも、Owinならば、GlimpseがOwinに対応すればそれだけで乗っかることが出来る(そして実際、現在対応作業中のようです)。New Relicのような監視ツールなどもそう、俺々フレームワークであっても、そういうのにフルに乗っかっていけるってのが、今までと違うところだし、だから、作ってもOKの許容ラインに達しやすくなったと思いますですよ。

私も、LightNodeのようなフレームワークレベルのものだけじゃなく、他のフレームワークで使える小さなMiddlewareをこっそり作って公開してたりします。一つはOwinRequestScopeContextで、HttpContext.CurrentのようなものをOwin上で使えるようにするもの。もう一つはRedisSessionで、その名の通り、裏側がRedisのセッションストアです。RedisのHash構造に格納していて、リクエスト開始時に全部のデータを読み込み、リクエスト実行中のアクセスは全てインメモリで完結さえ、リクエスト終了時に変更があったもの差分だけを書き出す(RedisのHash構造だからこそ可能)ようにしています。実はこれの原型は既に謎社で実稼働していて、沢山のアクセスを捌いている実績アリだったりして。

今後RubyのRackにある便利Middlewareが移植されたりとかもするんじゃないでしょうか、むしろ良さそーな発想のものは自分達で移植してみるのもいいかもしれません。Owinが出たことで、自分達で作ることが、独善じゃなく発展の道になった。

One ASP.NET。You。使うだけじゃなく作る。それがこれからのASP.NETの未来だと思います。

Related Works

WCF。なんのかんのいってWCF。は偉いねえ、壮大だねえ、とか。LightNodeはWCFのABCからBindingを抜いたようなイメージでいいですよ。で、やっぱWCFとかの、その手の抽象化は辛い!何か被せて共通化して出来た気がするのは誰も満足させられないパターン。

rpcoder。Aimingさんの、独自IDL(Interface Definition Language)からUnity用のC#コードとかを吐き出すもの。LightNodeとの違いは、IDLかどうか、かしらん。LightNodeはIDLじゃなくてサーバーサイドの実装そのものが定義になるので、そういった外部定義不要なので、手間削減と、実装との乖離が絶対にないってとこかしらん。

似たようなというか定義という点ではRAMLとかね、まぁRAMLは最悪かなぁって思うのですけれど。RESTfulの呪縛に囚われて極北まで行くとそうなるのかねえ。どうぞ素敵なモデリングをしてください。ほんとdoudemoii。

Google Cloud Endpoints。サーバーの実装があって、そこからiOSやAndroid用のコードを生成するってもの。いいですねー、これですよこれ。Cloud Endpointsの正式リリースはついこないだですが、(特に)モバイル向けのバックエンドはこういうのがベストだと本当に思いますし、RPCの時代というかそういったようなものの時代への揺り戻しというか、再び多様性の時代が来たかな、と、健全で素敵です。

ServiceStack。これは、WCF Alternativeの中では一番メジャーな選択肢、ではあるのだけど、正直、なんか、この人のAPIセンスは……。辛い。正直ナシです。ちなみにv4から有料化しました。

Finagle。Twitter製の、Scalaでできた非同期でプラガブルなRPCフレームワーク。非同期なので全部Future(C#のTaskみたいなもの)。Relatedといったけど特に直接的な影響はないけど、オサレでモダンなフレームワークがRPC、というところだけちょっと強調とか。

DuoVia.Http。Owinで動くLightweightのService Libraryということで、LightNodeに一番近い先行実装ですね!クライアント側はプロキシによる動的生成なので非同期なし。サーバー側がrefやoutに対応させたりとか多機能を狙いすぎて、実行速度が引っ張られてたりとか、ちょっと違うかな、と。

ASP.NET Web API。まぁ、散々腐しましたけれど、実際ふつーに選ぶのならASP.NET Web APIが最初の選択肢だと思います。悪くないですよむしろイイですよ。そもそもLightNodeの実装にあたっては50分で掴み取る ASP.NET Web API パターン&テクニックとかOne ASP.NET, OWIN & Katanaとかガン見してたので味噌先生には頭が上がらないのでWeb APIいいんじゃないでしょうか(適当)。真面目な話、ASP.NET Web APIが一番参考にしてるのは間違いないですので、話の流れ(?)で色々腐しましたが、良いと思いますよ、本当。

Conclusion

One ASP.NETと言いつつも別にフィーチャーされないYou!の部分を推してみました。人昔前は、こういった俺々フレームワークが乱立しないのが.NETの良さ、と言われていた、こともありました。ありました。過去の話です。世界の進化は速く、Microsoftだけが一手に全ての需要を引き受けられるわけがない。それぞれの需要に合わせて、時に組み合わせて、時に自分で作り上げることができる。そういった世界の幕開けがOwinです。まだまだMiddlewareは足りていないので、「組み立てる」にはならないでしょう、けれどそれを解決するためにも、自分達で作り、公開していきましょう?それがOpenな世界だし、これからのC#コミュニティのあるべき姿だと思っています。

(いつもやるやる詐欺で毎回言ってる気がしますが)LightNodeはコンセプトだけじゃなく、真面目に育てていきたいと思っています。そもそも、会社として、この辺の通信が来年は重要課題になってくるなあ、というのがあって考えてたものなので、諸々色々で半年後ぐらいには十分な完成度で掲示できるかなあ、って思いますですよ。勿論、皆さん今から使ってくれたら嬉しいですにぇ。

また、コンセプト語るには実装がなきゃ、と相当思っていまして。かつて人々は「パターン」「契約による設計」などアイデアに名前をつけて論じたけれど、 このごろの新しいアイデアはフレームワークやプログラミング言語、データベースエンジンなどを通じて表現されるようになった。 今は書籍ではなく実装が思想を表現する手段になっていると、Eric Evans(DDD本の人)は語った。そんなわけで、というわけではないですけれど、私は私の思想はコードで表現していきたいと思っているし、そもそもそうしてきた。linq.js(LINQが言語を超えることを)もChaining Assertion(流れるようなインターフェイスや英語的なるものの馬鹿らしさを)もReactiveProperty(全てが繋がるイメージを)もそうです。ライブラリは思想の塊なのです、言葉に出されていなければそこに思想はない?そんなことはなく、ずっと流暢に語ってくれるはず。

そしてC#の強さの証明は、会社の結果で表現していきます。実証されなければ何の意味もないし、何の説得力もない。誰に?というと、日本に、世界に。というわけで、引き続き来年の諸々にもご期待ください!

Prev |

Search/Archive

Category

Profile


Yoshifumi Kawai
Microsoft MVP for Visual C#

April 2011
|
March 2015

Twitter:@neuecc