Unity + iOSのAOTでの例外の発生パターンと対処法
- 2014-07-01
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-05-13
2014/2/8に北海道のCLR/HでOwinについて話してきたんですが、なんと!今の今までスライド公開してなかった!これはひどい!3ヶ月放置してた!熟成肉!ウルフギャング!ということでやっとこさ公開。若干加筆してあります。
前半は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のすすめ
- 2014-04-10
追記: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を何で書くか、のお話。
- MonoDevelop
標準同梱、色々なプラットフォームで動くし、ちゃんとIDEなので決して悪くない。が、Unityについてくるバージョンは古い場合が多く、そのためバグが残っていたり。また、日本語の入力が大変問題が多く叫ばれていたりする。
- 外部のテキストエディタ
SublimeやVim、Emacsなど。特にSublimeはよく使われているよう。非常に軽快で悪くない、とはいえ、IDEの持つ多くの機能を当然持っていないわけで、機能としては劣るといえる。SublimeはSublimeSocketAssetを入れると補完(弱)とかエラー表示とかは少しまかなえるようですけれど、Vimとかもそうですね、頑張れはするのだけれど、最終的にIDEに及ぶかというと、まあ、頑張れる。頑張れはする。
- Unityエディタ上のスクリプトエディタ
UnIDEとか。カジュアルな編集にはベンリだけれど専用に使っていけるか、というと……。
- 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#生活を満喫して欲しい/したいところですねー。
Microsoft MVP for Visual C#を再々々受賞しました
- 2014-04-02
今年も受賞できました。4年目です。去年の受賞した頃は謎社が始動し、タイトルもリリースされ、新しい人が入社しだす頃でした。その頃はまだPHPで実装されていたのですが、C#に移行するプロジェクトをちょうど始めた頃です。
C#による圧倒的な成果、C#だからこその強さ、というのを現実に示していく
というのを所信表明として掲げたわけですが、この一年で、成果は出すことができました。C#への移行は成功し、その強さ、体制は誇ることができると思っています。とはいえまだ、やっとスタート地点に立てたばかり。世間への認知は全然されていないでしょう。もっともっと強く、私にしかできないことを。
さて、個人として、MVPのありようというのはScott HanslemanのアナウンスChanges in the Microsoft MVP Program - MVPs for Open Source Contributionsのアナウンスの通りかな、と。ただのMicrosoftの広告塔だけじゃない貢献をしなければならない。そしてコードを現実に落としこむ馬力が求められるのではないか、と。
私はそうしてきたつもりですし、これからも、もっと、他の言語圏と較べても、より先進的に、そしてより実践的に。いつまでも.NETの、Microsoftのローカルな世界で閉じている場合ではない。C#こそが魅力的な選択であるということを、あらゆる角度から示し続けたいと思います。引き続き、今年もよろしくお願いします。
ForEachAsync - 非同期の列挙の方法 Part2
- 2014-03-14
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クライアント/コマンドレット
- 2014-03-11
というものを作りました。
- GitHub - RespClient
- PM> Install-Package RespClient
例によってインストールは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#での動的なメソッド選択における定形高速化パターン
- 2014-01-27
動的なメソッド選択、といってもなんのこっちゃというわけですが、身近な例だと、ようするに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フレームワークパフォーマンス比較大全
- 2014-01-14
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作成ガイド
- 2014-01-06
あけおめました。振り返る~系の記事はこっ恥ずかしいのでいつまでも先頭に出ていると嫌なので、割と流したくて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では以下の様な話が。
すべてがプラガブルで、モノリシックなアプリケーションフレームワークを持つ理由がもはや一つもないような未来を思い描いていました。すべてライブラリ、ミドルウェア、デコレータでまかなえるからです。 悲しいことに、そんな理想的な未来はやってきませんでした。
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知りたい、どう動かすのか見てみたい、って人もどうぞ。めととは。
2013年を振り返る
- 2013-12-30
振り返るシリーズ第三弾。毎年、30日に振り返っているので、今年も30日で。ちなみに12/30は私の誕生日でして、ついに30歳を迎えてしまった……。20代さようなら、いや、別にいいんですが、C#er若くない人サイドに入ったな!という感じなわけでして、新陳代謝がほげもげとか。
2011年はMVP受賞、2012年はgloopsへの入社と退社(在籍期間たったの10ヶ月だった!)、というわけですが、では今年のメイントピックは、やはり当然グラニ a.k.a.謎社の始動です。2012年末の段階では
今はニート。ではなく、謎社にいます。謎社ってなんだよというか伏せる意味は特にないんですが、まぁまだ伏せておきます。実際のとこ出来たばかりの会社でして、だいたいほぼほぼ創立メンバーとして働いてます。そして現在のところPHPが95%でC#が5%といったところですが(私もPHP書いてますよ!毎日吐き気が!)、直近の目標はC#比率を高めることです(笑)
来年は変化というよりは進化、↑で書いたとおりにゲームを、じゃあなくて会社を前身させるのに全力で突き進む、というわっかりやすい目標があるんで、そのとーりに邁進しましょう。C#といったら謎社!みたいな、C#を使う人が憧れるぐらいな立ち位置の会社にできればいいなと思っています。
という話を立てていたわけですが、どうでしょう?かなりの部分で達成出来たのではないかと思います。会社は信じられないぐらいの成功を果たしていますし(※別に私の力ではなくてメンバー全員の力の賜物です)、当初PHPで書かれていたプログラムは、100%、C#へのリプレイスを果たしました。ボロ一軒家(リリース前は会社=一軒家にすし詰めで開発してた)でPHP書いてる時から、こういったヴィジョンを描いていたし、1年のうちに実現しきったのは、相当やった方だと年の末ぐらいは自画自賛させてくださいな。
といっても、まだ「C#を使う人が憧れるぐらいな立ち位置の会社」になれているかといったら、知名度であったり、そしてまだまだ実績も足りていないので、全然、目指す水準には満たしていないです。まだ、やっと0→1に、スタート地点に立ったばかり。ここからは1→100にしていかなければならない。また、外向きだけではなく、内側もまた全然整備しきれてないので、働く人がここで働くことに満足できる状態を作れなきゃとか、やることは山積み。
C#
今年のブログ内容は一気に非同期に傾いています。というのも会社で本格的にasyncを導入して使いまくり始めたこともあって、実践的に地雷を踏みまくってノウハウが溜まったからです。こういう実践的な話は、リファレンス情報だけではどうしても足りないわけで、両方が必要なのです。よく、C#はMSDNに情報がいっぱいあって、こんなに充実している言語、他にないよ!何が不満なの!?というけれど、半分合ってて、実態としては全然あってない。あくまで実践的な情報が重要度では第一、リファレンスは補完するもの。だから不満に感じるのは当然です。Microsoftとしては、そういう不足はコードレシピで補いたいようだけれど、それじゃ補えないというか、結局こういうのってリファレンス側に近い情報であって、欠落を埋められるわけがない。
じゃあ誰が埋めるのか、埋められるのかって、それは私達自身だけでしょう。自らの知見から来る情報がネットに溢れるといいな、と思っていますし、そのためにもまず自分たちがやっていきます。特に.NET界隈は実地的な話がなくて。海外とのレベル格差も酷い。圧倒的に日本はレベルが低い、ように見えてしまう。実際は優れた人は表に出てこないとか、絶対量が違うからとか、幾らでも言い分もあるし、確かにそうなのでしょう。でも、やっぱり、見えなければ意味がないし、その結果が、これ。C#という言語の他言語に比べた地位の低さ。例えばですよ、AWSは沢山の実地的な話が溢れてる。かたやAzureは、ただのリファレンス情報が垂れ流されているだけ。こういうの地味にきっついし差として現れるんだよね。C#も同じようなものですよ、現状。残念ながら。
というわけで超積極的に、情報は出していきたいのですにぇ。
さて、個人的には相変わらず小さなライブラリは作りまくってました。NuGetの登録数も36になりました。そういえばAsyncOAuthも今年からですね、おかげ様でOAuthライブラリといったらこれだよね!ぐらいに受け入れてもらえたようで、色々なところで使われているようです。謎社自身でもがしがし使ってます。
今年もう一つ、注力していたのはCloudStructuresというRedisクライアントですね。C# + Redis、しかもC# 5.0推奨というハードルの高さすぎてあまり使われてる感はないですが、これは謎社でハイパー使ってます。
AsyncOAuthやCloudStructuresは、謎社でのPHP→C#移行で絶対必要になるパーツという目算だったので先行して仕上げていました。その路線に乗っかって、来年育てていくのはLightNodeですね。こういう技術、必要になってから調べ始めているのでは遅く、でもあまり長いスパンで見ていてもしょうがない。研究開発機関ではないのだから。というわけで、今のところ、半歩先ぐらいを見据えて動いています。
ハイパー放置状態のlinq.jsを毎年何とかするする詐欺は、来年こそ、は……。
会社
今では当たり前のようにC#の企業ですが、当初はPHPでした。6/8時点で講演した.NET最先端技術によるハイパフォーマンスウェブアプリケーションは、はてブ300以上、現時点でViewsが33500と、.NET系にしてはクリティカルヒットを飛ばしましたが、スライドの冒頭で紹介したとおり、この時点ではPHPだったのです。実際にリプレースが完了したのは7/16日。深夜メンテナンス時間を挟んで朝に一気に切り替え。若干のトラブルはあったものの無事完了。
今年最大のハイライトであり、というか私自身も今のところ生きてて一番のハイライトですね。出来てまもない会社だから、体力も人員もない、でも凄い上り調子だからアプリを止めるとかありえないどころか育てなければならない。そんな中でリソース分散してPHPとC#を並走させて、統合、そして切り替え。まず、やるって決定が常識的に考えてありえないほど無茶苦茶だし、実際にやりきったのも凄いなぁと自画自賛Part2。やれるはずっていう机上の空論と、実際にやりきるってのは、違いはないけど必要な体力と精神力が全然違いますね、もうほんとヤバかった、特に精神的に……。
これに関しては、メンバーに恵まれました。出来たばかりの先行き不明な会社の、しょぼいホームページに、たった二行で求人情報が書かれててmailtoで送れ、というような状況で、素晴らしい人が次々と来てくれたのは信じられない話で。ほんと感謝の念に尽きません。
逆に今のほうが求人に苦戦しているのがアレ。あ、そんなわけでWe're Awaitingということで積極的に採用はしているのでいつでも歓迎ですよ - 求人ページ ← 未だにサイトしょぼい
職種はCTOなのですけれど、CTO論をぶったりは、しません特に。色々なところにボーダーがあって、そこを超えそうになったら発言するようにしているのと、つまり超えない時は何もやってないように見えて実際何もやってない!こたぁない。って感じですかね(テキトー)。まぁ、かくあるべきみたいなものはあります、私の中で、ちゃんと。どんぐらい全うしてるかというと、うーん、70点?
技術的な方向性はシンプルに最先端のC#、なわけですけれど、それが良いことなのかどーか。勿論、良いことです。でも必ずしも良いことと言えるわけじゃあない。じゃあ良いことに変えればいいだけです。こんなの単純な話で、よーは会社として保守的にならないことがバリューを産み出せるようになりゃあいいだけです。逆に、何も産み出せないならNGでしょうね。そうならないように何が出来るかを行動していくべきでしょう。
ゲーム
Super Hexagon最高!これに尽きる。マジ最高。これはヤヴァい。Windows Phoneの最大の欠点はSuper Hexagonがプレイできないことと断言していい。絶対無理ー、と思ったHYPER HEXAGONESTをクリアした瞬間とか、ゲームの達成感の全てが詰まってた。超興奮。数年ぶりにゲームの面白さを思い出させてくれたマスターピース。
というわけで、今年は圧倒的に超えられない壁にSuper Hexagonが存在しているのですが、次点としてはGAMELOFTのモバイル用レースゲーム、Asphalt 8かな。グラフィックのケレン味が実にモバイル向けで、スマートフォン向けゲームとしては現時点で最高のグラフィック品質。ゲームのほうも大味、じゃなくてダイナミックで楽しい。通信対戦もあるしね。
そしてWindows Store App版もある!しかもWindows Phone 8版もある!しかもWindows Store App - Windows Phone 8間での通信対戦が可能!(iPhone-AndroidとかiPhone-WP8とかは対戦不可)。そんなわけで社内Windowsクラスタで話題騒然、Surface持っているならマストバイ、とか微妙な盛り上がりを見せました。ていうか社内で普通にWindows Phone 8の実機が集まるところがまずアレ。
本とか映画とか漫画とかアニメとか
見てない!ゲームと同じく、この辺りも年々見なくなっていってますが今年は特に一番見てない気がする、忙しさのせいかな(言い訳)。引っ越しして、実家に預けていた本とか漫画を全部回収したのだけど、あー、学生の頃は、いっぱい読んでるわけでもないけれどまぁまぁ読んでたなぁ、とか寂しくなったりはしたりして。
来年
ここ数年は、毎年ジェットコースター状態で目まぐるしく変化していて。けれど、大きな目標からはブレないで、年々近づけている気がします。一番最初に若くない人サイドに入ったとか、新陳代謝とか言いましたが、来年はそういうことが起こる状態を作っていきたいですね。C#が、若い人がこぞって使うような言語になってればいい、と。そのためにできること。人がすぐに思い浮かべられる、メジャーなアプリケーションの創出と、C#による圧倒的な成果、C#だからこその強さ、というのを現実に示していくこと。雇用の創出、の連鎖。
というわけで、来年も引き続きご期待くださいだし、よろしくお願いします。
LightNode - Owinで構築するMicro RPC/REST Framework
- 2013-12-23
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から。
- Install-Package LightNode.Server
細かいパッケージが実はいっぱいあったりして……。
- Install-Package LightNode.Client.PCL.T4
- Install-Package LightNode.Formatter.JsonNet
- Install-Package LightNode.Formatter.ProtoBuf
- Install-Package LightNode.Formatter.MsgPack
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#の強さの証明は、会社の結果で表現していきます。実証されなければ何の意味もないし、何の説得力もない。誰に?というと、日本に、世界に。というわけで、引き続き来年の諸々にもご期待ください!
An Internal of LINQ to Objects
- 2013-12-16
というタイトルで、大阪で開催された第3回 LINQ勉強会で発表してきました。
大阪は初めてですね!というか、東京以外で発表しに行くのは初めてです。大阪遠い。
レベル感は、まぁもうLINQも初出から10年も経つわけだし(経ってない)、もはや初心者向けもないだろうということで、LINQ to ObjectsのDeep Diveなネタを突っ込んでおきました。こんなにまとまってる資料は世界にもないですよ!なんで知ってるかというと、linq.jsの実装で延々と何回も書いてるからです、はい。いいことです。そのぐらいにはパーフェクトな実装ということで。ver.3の完成は、も、もう少し、ま、まだ……。ごめんなさい。近いうちには、またベータ出すよ!←いい加減完成させろ
口頭で捕捉した内容としては、yield returnで書くメソッドは引数チェック用のと分離させよう、というところ。これ、メンドーくさかったらやらなくていいです。実際メンドウクサイですしね。コアライブラリっぽい位置づけのものだったらがっつしやるのも良いとは思いますが、普段からやるのはカッタルイでしょう。と、いっても、LINQ以降はあまり生でyield return使うこともないでしょうけれど。
イテレータの話とかは、実際doudemoiiんですが、気になる人は、これはそもそもC#の言語仕様書に書いてあります。言語仕様書はVSインストールディレクトリの
C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC#\Specifications\1041\CSharp Language Specification.docx
にあるので(日本語訳されてるよ!)、読んだことない人は眺めてみると楽しいんではかと思います。
非同期時代のLINQ
- 2013-12-04
この記事はC# Advent Calendar 2013の4日目となります。2012年はMemcachedTranscoder - C#のMemcached用シリアライザライブラリというクソニッチな記事で誰得でした(しかもその後、私自身もMemcached使ってないし)。その前、2011年はModern C# Programming Style Guide、うーん、もう2年前ですかぁ、Modernじゃないですねえ。2011年の時点ではC# 5.0はCTPでしたが、もう2013年、当然のようにC# 5.0 async/awaitを使いまくる時代です。変化は非常に大きくプログラミングスタイルも大きく変わりますが、特にコレクションの、LINQの取り扱いに癖があります。今回は、非同期時代においてLINQをどう使いこなしていくかを見ていきましょう。
Selectは非同期時代のForEach
これ超大事。これさえ掴んでもらえれば十二分です。さて、まず単純に、Selectで値を取り出す場合。
// こんな同期版と非同期版のメソッドがあるとする
static string GetName(int id)
{
return "HogeHoge:" + id;
}
static async Task<string> GetNameAsync(int id)
{
await Task.Delay(TimeSpan.FromMilliseconds(100)); // 適当に待機
return "HogeHoge:" + id;
}
// 以後idsと出てきたらこれのこと指してるとします
var ids = Enumerable.Range(1, 10);
// 同期バージョン
var names1 = ids.Select(x => new { Id = x, Name = GetName(x) }).ToArray();
// 非同期バージョン
var names2 = await Task.WhenAll(ids.Select(async x => new { Id = x, Name = await GetNameAsync(x) }));
ラムダ内でasyncを書き、結果はIEnumerable<Task<T>>となるので、配列に戻してやるためにTask.WhenAllとセットで使っていくのが基本となります。Task.WhenAllで包むのはあまりにも頻出なので、以下の様な拡張メソッドを定義するといいでしょう。
// こういう拡張メソッドを定義しておけば
public static class TaskEnumerableExtensions
{
public static Task WhenAll(this IEnumerable<Task> tasks)
{
return Task.WhenAll(tasks);
}
public static Task<T[]> WhenAll<T>(this IEnumerable<Task<T>> tasks)
{
return Task.WhenAll(tasks);
}
}
// スッキリ書ける
var names2 = await ids.Select(async x => new { Id = x, Name = await GetNameAsync(x) }).WhenAll();
では、foreachは?
// 同期
foreach (var id in ids)
{
Console.WriteLine(GetName(id));
}
// 非同期
foreach (var id in ids)
{
Console.WriteLine(await GetNameAsync(id));
}
そりゃそーだ。……。おっと、しかしせっかく非同期なのに毎回待機してループしてたらMottaiなくない?GetNameAsyncは一回100ミリ秒かかっているから、100*10で1秒もかかってしまうんだ!ではどうするか、そこでSelectです。
// 同期(idsがList<int>だとする)
ids.ForEach(id =>
{
Console.WriteLine(GetName(id));
});
// 非同期
await ids.Select(async id =>
{
Console.WriteLine(await GetNameAsync(id));
})
.WhenAll();
ForEachの位置にSelect。ラムダ式中では戻り値を返していませんが、asyncなので、Taskを返していることになります(Task<T>ではなく)。同期ではvoidとなりLINQで扱えませんが、非同期におけるvoidのTaskは、Selectを通ります。あとはWhenAllで待機してやれば出来上がり。これは全て同時に走るので100msで完了します。10倍の高速化!
ただし、この場合処理順序は保証されません、同時に走っているので。例えばとある時はこうなりました。
HogeHoge:1
HogeHoge:10
HogeHoge:8
HogeHoge:7
HogeHoge:4
HogeHoge:2
HogeHoge:6
HogeHoge:3
HogeHoge:9
HogeHoge:5
処理順序を保証したいなら?WhenAll後に処理ループを回せばいいぢゃない。
// こうすれば全て並列でデータを取得したあと、取得順のままループを回せる
var data = await ids.Select(async id => new { Id = id, Name = await GetNameAsync(id) }).WhenAll();
foreach (var item in data)
{
Console.WriteLine(item.Name);
}
一旦、一気に詰めた(100ms)後に、再度回す(0ms)。これはアリです。そんなわけで、非同期時代のデータの処理方法は三択です。逐次await, ForEach代わりのSelect, 一気に配列に詰める。どれがイイということはないです、場合によって選べばいいでしょう。
ただ言えるのは、超大事なのは、Selectがキーであるということ、ForEachのような役割を担うこと。しっかり覚えてください。
非同期とLINQ、そしてプリロードについて
さて、SelectだけではただのForEachでLINQじゃない。LINQといったらWhereしてGroupByして、ほげ、もげ……。そんなわけでWhereしてみましょう?
// 非同期の ラムダ式 をデリゲート型 'System.Func<int,int,bool>' に変換できません。
// 非同期の ラムダ式 は void、Task、または Task<T> を返しますが、
// いずれも 'System.Func<int,int,bool>' に変換することができません。
ids.Where(async x =>
{
var name = await GetNameAsync(x);
return name.StartsWith("Hoge");
});
おお、コンパイルエラー!無慈悲なんでなんで?というのも、asyncを使うと何をどうやってもTask<bool>しか返せなくて、つまりFunc<T,Task<bool>>となってしまい、Whereの求めるFunc<T,bool>に合致させることは、できま、せん。
Whereだけじゃありません。ラムダ式を求めるものは、みんな詰みます。また、Selectで一度Task<T>が流れると、以降のパイプラインは全てasyncが強いられ、結果として……
// asyncでSelect後はTask<T>になるので以降ラムダ式は全てasyncが強いられる
// これはコンパイル通ってしまいますがkeySelectorにTaskを渡していることになるので
// 実行時エラーで死にます
ids.Select(async id => new { Id = id, Name = await GetNameAsync(id) })
.OrderBy(async x => (await x).Id)
.ToArray();
Selectがパイプラインにならず、むしろ出口(ForEach)になっている。自由はない。
ではどうするか。ここは、一度、配列に詰めましょう。
// とある非同期メソッドのあるClassがあるとして
var models = Enumerable.Range(1, 10).Select(x => new ToaruClass());
// 以降の処理で使う非同期系のメソッドなり何かを、全てawaitで実体化して匿名型に詰める
var preload = await models
.Select(async model => new
{
model,
a = await model.GetAsyncA(),
b = await model.GetAsyncB(),
c = await model.GetAsyncC()
})
.WhenAll();
// そうして読み取ったもので処理して、(必要なら)最後に戻す
preload.Where(x => x.a == 100 && x.b == 20).Select(x => x.model);
概念的にはプリロード。というのが近いと思います。最初に非同期なデータを全て取得しまえば、扱えるし、ちゃんと並列でデータ取ってこれる。LINQの美徳である無限リストが取り扱えるような遅延実行の性質は消えてしまいますが、それはshoganai。それに、LINQにも完全な遅延実行と、非ストリーミングな遅延実行の二種類があります。非ストリーミングとは、例えばOrderBy。これは並び替えのために、実行された瞬間に全要素を一度蓄えます。例えばGroupBy。これもグルーピングのために、実行された瞬間に全要素を舐めます。非同期LINQもまた、それらと同種だと思えば、少しは納得いきませんか?現実的な妥協としては、このラインはアリだと私は思っています。分かりやすいしパフォーマンスもいい。
AsyncEnumerableの幻想、或いはRxとの邂逅
それでも妥協したくないならば、次へ行きましょう。まだ手はあります、良いかどうかは別としてね。注:ここから先は上級トピックなので適当に読み飛ばしていいです
そう、例えばWhereAsyncのようにして、Func<T,bool>じゃなくFunc<T,Task<bool>>を受け入れてくれるオーバーロードがあれば、いいんじゃない?って思ってみたり。こんな風な?
public static class AsyncEnumerable
{
// エラー:asyncとyield returnは併用できないよ
public static async IEnumerable<T> WhereAsync<T>(this IEnumerable<T> source, Func<T, Task<bool>> predicate)
{
using (var e = source.GetEnumerator())
{
while (e.MoveNext())
{
if (await predicate(e.Current))
{
yield return e.Current;
}
}
}
}
}
ただ、問題の本質はそんなことじゃあない。別にyield returnが使えなければ手書きで作ればいいわけで。そして作ってみれば、本質的な問題がどこにあるのか気づくことができます。
class WhereAsyncEnumerable<T> : IEnumerable<T>, IEnumerator<T>
{
IEnumerable<T> source;
Func<T, Task<bool>> predicate;
T current = default(T);
IEnumerator<T> enumerator;
public WhereAsyncEnumerable(IEnumerable<T> source, Func<T, Task<bool>> predicate)
{
this.source = source;
this.predicate = predicate;
}
public IEnumerator<T> GetEnumerator()
{
return this;
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public T Current
{
get { return current; }
}
object System.Collections.IEnumerator.Current
{
get { return Current; }
}
public void Reset()
{
throw new NotSupportedException();
}
public void Dispose()
{
}
// ↑まではdoudemoii
// MoveNextが本題
public bool MoveNext()
{
if (enumerator == null) enumerator = source.GetEnumerator();
while (enumerator.MoveNext())
{
// MoveNextはasyncじゃないのでawaitできないからコンパイルエラー
if (await predicate(enumerator.Current))
{
current = enumerator.Current;
return true;
}
}
return false;
}
}
MoveNextだけ見てもらえればいいのですが、predicateを使うのはMoveNextなわけです。ここがasyncじゃないと、AsyncなLINQは成立しません。さて、もしMoveNextがasyncだと?
public async Task<bool> MoveNext()
{
// ここで取得するenumeratorのMoveNextも
// 全て同一のインターフェイスであることが前提条件なのでTask<bool>とする
if (enumerator == null) enumerator = source.GetEnumerator();
while (await enumerator.MoveNext())
{
if (await predicate(enumerator.Current))
{
current = enumerator.Current;
return true;
}
}
return false;
}
これは機能します。MoveNextをasyncにするということは連鎖的に全てのMoveNextがasync。それが上から下まで統一されれば、このLINQは機能します。ただ、それってつまり、IEnumerator<T>を捨てるということ。MoveNextがasyncなのは、似て非なるものにすぎない。当然LINQっぽい何かもまた、全て、このasyncなMoveNextを前提にしたものが別途用意されなければならない。そして、それが、Ix-Async。
Ix-Asyncのインターフェイスは、上で出したasyncなMoveNextを持ちます。
public interface IAsyncEnumerable<out T>
{
IAsyncEnumerator<T> GetEnumerator();
}
public interface IAsyncEnumerator<out T> : IDisposable
{
T Current { get; }
Task<bool> MoveNext(CancellationToken cancellationToken);
}
そして当然、各演算子はIAsyncEnumerableを求めます。
public static IAsyncEnumerable<TSource> Where<TSource>(this IAsyncEnumerable<TSource> source, Func<TSource, bool> predicate);
これの何が便利?IEnumerable<T>からIAsyncEnumerable<T>へはToAsyncEnumerableで変換できはするけれど……、求めているのはIEnumerable<Task<T>>の取り扱いであったりpredicateにTaskを投げ込めたりすることであり、何だかどうにもなく、これじゃない感が否めない。
そもそも、LINQ to Objectsから完全に逸脱した新しいものなら、既にあるじゃない?非同期をLINQで扱うなら、Reactive Extensionsが。
Reactive Extensionsと非同期LINQ
ではRxで扱ってみましょう。の前に、まず、predicateにTaskは投げ込めません。なのでその前処理でロードするのは変わりません。ただ、そのまま続けてLINQ的に処理可能なのが違うところです。
await ids.ToObservable()
.SelectMany(async x => new
{
Id = x,
Name = await GetNameAsync(x)
})
.Where(x => x.Name.StartsWith("Hoge"))
.ForEachAsync(x =>
{
Console.WriteLine(x);
});
おお、LINQだ?勿論、Where以外にも何でもアリです。RxならLINQ to Objects以上の山のようなメソッドを繋げまわることが可能です。ところで、ここで出てきているのはSelectMany。LINQ to ObjectsでのSelectの役割を、Rxの場合はSelectManyが担っています。asyncにおいてForEachはSelectでRxでSelectはSelectMany……。混乱してきました?
なお、これの結果は順不同です。もしシーケンスの順序どおりにしたい場合はSelect + Concatを代わりに使います。
await ids.ToObservable()
.Select(async x => new
{
Id = x,
Name = await GetNameAsync(x)
})
.Concat()
.Where(x => x.Name.StartsWith("Hoge"))
.ForEachAsync(x =>
{
Console.WriteLine(x);
});
ソーナンダー?ちなみにSelectManyはSelect + Mergeに等しい。
await ids.ToObservable()
.Select(async x => new
{
Id = x,
Name = await GetNameAsync(x)
})
.Merge()
.Where(x => x.Name.StartsWith("Hoge"))
.ForEachAsync(x =>
{
Console.WriteLine(x);
});
この辺のことがしっくりくればRxマスター。つまり、やっぱRxムズカシイデスネ。とはいえ、見たとおり、Rx(2.0)からは、asyncとかなり統合されて、シームレスに取り扱うことが可能になっています。対立じゃなくて協調。自然に共存できます。ただし、単品でもわけわからないものが合わさって更なるカオス!強烈強力!
まとめ
後半のAsyncEnumerableだのIx-AsyncだのRxだのは、割とdoudemoii話です、覚えなくていいです。特にIx-Asyncはただの思考実験なだけで実用性ゼロなので本気でdoudemoiiです。Rxは便利なので覚えてくれてもいいのですが……。
大事なのは、async + Selectです。SelectはForEachなんだー、というのがティンとくれば、勝ったも同然。そして、プリロード的な使い方。そこさえ覚えれば非同期でシーケンス処理も大丈夫。
asyncって新しいので、今まで出来たことが意外と出来なくてはまったりします。でも、それも、どういう障壁があって、どう対処すればいいのか分かっていればなんてことはない話です。乗り越えた先には、間違いなく素晴らしい未来が待っているので、是非C# 5.0の非同期、使いこなしてください。
GlimpseによるRedis入出力の可視化とタイムライン表示
- 2013-11-15
空前のGlimpseブーム!Glimpse最高!これ入れてないASP.NET開発はレガシー!というわけで、グラニ a.k.a. 謎社でも激しく使用を開始しています。さて、ではGlimpseとは何ぞやか、という話は今回は、しません(!)。それは誰かがしてくれます(チラッ。今回は本題のRedis周りのGlimpse拡張について。
CloudStructures 0.6
グラニではRedisライブラリとしてBookSleeve + CloudStructuresを利用しています。RedisやBookSleeveについては、C#のRedisライブラリ「BookSleeve」の利用法を読んでね!何故BookSleeveだけじゃないのかというと、BookSleeveだけだと使いづらいからです。例えるなら、BookSleeveはRedisドライバであり、ADO.NETみたいなもの。CloudStructuresはO/R(Object/Redis)マッパーであり、Dapperのようなもの。といった関係性で捉えてもらえればあってます。で、CloudStructuresは私謹製です。
今回Glimpseと連携させて使いやすくログ吐きするのに若干足りてなかったので、出力用のインターフェイス ICommandTracer を破壊的変更かけてがっつし変えちゃってます。というわけでバージョン0.6に更新。破壊的変更、ま、まだ0.xだし……。0.xとはいえ、CloudStructuresはグラニのプロダクション環境下で激しく使われてます。もんのすごい量のメッセージを捌いている(BookSleeve開発元のStackOverflowよりも遥かに捌いているはず)、秒間で数万とか数十万とかのクエリ数を、なので実績は十二分にあると言えるでしょう。
今回からリポジトリにCloudStructures.Demo.Mvcというプロジェクトを入れているので、実環境でどういう風に使えばいいのかとか、Glimpseでどう表示されるのかが分かるようになってますので、使う時は参考にどうぞ。
Glimpseによる表示
今回からCloudStructuresによるRedisアクセスを可視化できるように、Glimpseプラグインが別途インストールできるようになりました。NuGet上で Glimpse.CloudStructures.Redisから入れればおk。インストール後、Glimpseを有効にし、ICommandTracerにGlimpse.CloudStructures.Redis.RedisProfilerを渡してやると(Web.configからの設定の仕方などはCloudStructures.Demo.Mvcを見て下さい)、以下のようになります。
Redisタブが追加され、Redisで、どのコマンドでどのキーで実行したか、何を送って何を受け取れたか、かかった時間。それらが一覧表示されます。また、重複コマンド・キーで発行した場合は警告表記になります(一番下のオレンジの)
とにかく、通信が全部見える。中身含めて。圧倒的に捗ります。これがないなんて、盲目で歩いているに等しかった。ありえない。もう、戻れない。見えるっては、いいことですねぇ、ほんと。
更に、Glimpse標準のTimelineタブに統合されていて、これが超絶最高です。
5つ連なってる部分、これは非同期に並列アクセスされています。コードは
var ids = new[] { 12, 3124, 51, 636, 6714 };
var rand = new Random();
await Task.WhenAll(ids.Select(async x =>
{
await RedisGroups.Demo.String<int>("TestInc.Id." + x).Increment(rand.Next(1, 10));
}).ToArray());
のようになっているわけですが、これ、もし同期アクセスだったら、同時に動くことなく、この分だけタイムラインが右に長くなるわけです。可視化されることで全て非同期!全てパイプライン!効率的に、高速に、というのがとてもわかりやすく見えます。実際、グラニではこういった一気に叩いてWhenAllというパターンを多用して高速なレスポンスを実現しています。ここまでASP.NET MVCの非同期コントローラーを死ぬほど使い倒している企業は、世界でも稀なのではないか、というレベルで非同期祭りやってます。(そして時に地雷踏みつつ前進してます)。
Glimpseについてもうちょっとだけ
実際のところGlimpse、ただ入れただけだとあんまり嬉しいことはないでしょう。色々なサーバーの情報が見えると言ったって、そんなの一度だけ見れば十分だしー、で終わってしまったり。ただ情報がありゃあいいってものじゃなくて、何の情報が必要か、が大事。でも、Glimpseってカスタマイズが容易なんですね。だから、その会社にとって、そのアプリケーションにとって大事な情報を流しこむことができる。大事な情報を流しこむようにすることで「頻繁に使うものではない」から「頻繁に使うもの」に変える。そうすることで、手放せないものとなります。
これはグラニで実際に使っているタブですが、Log, PlatformAPI, Redis, SQL Explainは弊社が独自に作成したタブです。以前にHttp, SQL, Redisのロギングと分析・可視化についてという記事を書きましたが、そこで、外部へのアクセスをフックする仕組みは作ってあったので、それぞれでGlimpseにデータ流すように変えただけです。これによりRedis, Http, SQLの全てがタブで全て見えるし、Timelineにも出てくるのでボトルネックが一目瞭然に。過度な通信なども一発で分かる。
SQL Explainはリクエスト中で発行された全てのSQLに対してexplainを発行し結果を表示しています(うちはMySQLを使っているのでexplainです、SQL Serverならそれ用のコマンド打てば同様以上のことが当然可能です)。これにより、将来ボトルネックになるクエリが早期発見できます。
explainとか面倒だから全てに律儀に打ったりしないし、*ms以上遅いクエリは警告するとかやったって、開発環境のデータ量だと無意味だったりするでしょう。だから、全てのクエリに対して自動でexplainをかけてやって、怪しそうなもの(using filesortとか)は最初から警告してあげる。これで、クソクエリの早期撲滅が可能となります。
ちなみにMiniProfilerとの使い分けですが、うちではむしろもうMiniProfilerイラナクね?の方向で進んでます。ちょっとまだGlimpse、足りてないところもあるのですが、割と直ぐに解決しそうだし、むしろMiniProfiler動かしてるとHistoryが汚れるのでGlimpseにとって邪魔なのですよね。
まとめ
といったように、グラニではより開発しやすい環境を作ることに全力を注いでいます。なんと今だとアプリケーションエンジニア・インフラエンジニア・フロントエンドエンジニアを募集中だそうですよ!←求人広告記事ダッタノカー、イヤソンナコトナイデスケドネ。なんか敷居高杉と思われてるかもですが、アプリエンジニアに関しては割とそんなことなく、基準は最低限LINQ to Objects知ってる程度からなので気楽にどうぞ。最近、オフィスを六本木ヒルズに移転しまして、ハードウェア環境も充実しているので、そちらの面でも開発しやすいかな、と。モニタも27インチ(2560x1440)をトリプルですしドリンク無料飲み放題ですし。
ソフトウェア側も良さそうならどんどんバシバシ変えてます。Glimpseも最初は導入されてませんでしたが、後から入れていますしね。ASP.NET MVCも早速5へ。この辺の姿勢は初期の文化が大事だと思っているので、絶対緩めないのを信条にしています。
なお、Glimpseの導入や独自タブの作成、ならびにGlimpse.CloudStructures.Redisの作成は、私がやったわけではありません!私がやったのはSQL Explainタブの作成とICommandTracerの改変、Glimpse.CloudStructures.Redisを社外に出せるよう依存部分の除去しただけでして。そんなわけで皆で一岩となって改革してるんですよー、とっても刺激的でいい職場です。私にとっても。←ヤッパリ求人広告記事ダッタノカー、イヤソンナコトナイデスヨ。
.NETのコレクション概要とImmutable Collectionsについて
- 2013-10-31
先週の土曜日に、「プログラミング .NET Framework 第4版 」座談会でOverview of the .NET Collection Framework and Immutable Collectionsとして、コレクションフレームワークとImmutable Collectionsについて話してきました。
案外コレクションについてまとまった話って、ない(or .NET 4.5からReadOnly系が入ってきて、話が更新されているもの)ので、資料として役に立つのではないかと思います。
Collection Framework
前半部分ですが、これのジューヨーなところはILinqable<T>、じゃなくて(スライド資料では出てないのでナンノコッチャですが)、ReadOnly系の取り扱いですね。MutableとReadOnlyが枝分かれしている理由とか対処方法とか、が伝えたかった点です。いやあ、コレクション作る時は両方実装しよう!とかしょうもないですねえ、shoganaiのですねぇ……。
IEnumerable<T>とIReadOnlyCollection<T>の差異は実体化されていない「可能性がある」かどうか。で、なのでメソッドの引数などで内部で実体化されてるのを前提にほげもげしたい場合は、IReadOnlyCollection<T>を受け取るほうが望ましいといえば望ましいのですが、汎用的にIEnumerableのままで……という場合は、以下のようなメソッドを用意しとくといいでしょう。
/// <summary>
/// sourceが遅延状態の場合、実体化して返し、既に実体化されている場合は何もせずそれ自身を返します。
/// </summary>
/// <param name="source">対象のシーケンス。</param>
/// <param name="nullToEmpty">trueの場合、sourceがnull時は空シーケンスを返します。falseの場合はArgumentNullExceptionを吐きます。</param>
public static IEnumerable<T> Materialize<T>(this IEnumerable<T> source, bool nullToEmpty = true)
{
if (nullToEmpty && source == null)
{
return Enumerable.Empty<T>();
}
else
{
if (source == null) throw new ArgumentNullException("sourceがnullです");
}
if (source is ICollection<T>)
{
return source;
}
if (source is IReadOnlyCollection<T>)
{
return source;
}
return source.ToArray();
}
こんなのを作って、冒頭で呼べば、二度読みなどもOKに。
public static void Hoge<T>(IEnumerable<T> source)
{
source = source.Materialize(); // ここで実体化する
// あとは好きに書けばいいのではないでせうか
}
どうでしょ。また、二度読みなら列挙したらキャッシュして、再度読む時はそっから読んでくれればいいのに!というリクエストあるかと思います。それは一般的にはメモ化(Memoization)といいます。というわけで、シーケンスに実装してみましょう。
public static IEnumerable<T> Memoize<T>(this IEnumerable<T> source)
{
if (source == null) throw new ArgumentNullException("sourceがnull");
return new MemoizedEnumerable<T>(source);
}
class MemoizedEnumerable<T> : IEnumerable<T>, IDisposable
{
readonly IEnumerable<T> source;
readonly List<T> cache = new List<T>();
bool cacheComplete = false;
IEnumerator<T> enumerator;
public MemoizedEnumerable(IEnumerable<T> source)
{
this.source = source;
}
public IEnumerator<T> GetEnumerator()
{
if (enumerator == null) enumerator = source.GetEnumerator();
return new Enumerator(this);
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Dispose()
{
if (enumerator != null) enumerator.Dispose();
}
class Enumerator : IEnumerator<T>
{
readonly MemoizedEnumerable<T> enumerable;
int index = 0;
public Enumerator(MemoizedEnumerable<T> enumerable)
{
this.enumerable = enumerable;
}
public T Current { get; private set; }
public void Dispose()
{
}
object System.Collections.IEnumerator.Current
{
get { return Current; }
}
public bool MoveNext()
{
if (index < enumerable.cache.Count)
{
Current = enumerable.cache[index];
index++;
return true;
}
if (enumerable.cacheComplete) return false;
if (enumerable.enumerator.MoveNext())
{
Current = enumerable.enumerator.Current;
enumerable.cache.Add(Current);
index++;
return true;
}
enumerable.cacheComplete = true;
enumerable.enumerator.Dispose();
return false;
}
public void Reset()
{
throw new NotSupportedException("Resetは産廃");
}
}
}
こうしておけば、
// hoge:xが出力されるのは1回だけ
var seq = Enumerable.Range(1, 5)
.Select(x =>
{
Console.WriteLine("hoge:" + x);
return x;
})
.Memoize();
// なんど
foreach (var item in seq.Zip(seq, (x, y) => new { x, y }).Take(4))
{
Console.WriteLine(item);
}
// ぐるぐるしても一度だけ
foreach (var item in seq.Zip(seq, (x, y) => new { x, y }))
{
Console.WriteLine(item);
}
といった感じ。Materializeより合理的といえば合理的だし、そうでないといえばそうでない感じです。私はMaterializeのほうが好み。というのもMemoizeは完了していないEnumeratorを保持しなければいけない関係上、Disposeの扱いがビミョーなんですよ、そこが結構引っかかるので。
あと、IEnumerable<T>ですが、スレッドセーフではない。そう、IEnumerable<T>にはスレッドセーフの保証は実はない。というのを逆手に取ってる(まぁ、それはあんまりなので気になる人はlockかけたりしましょう)。ちなみにReadOnlyCollectionだってラップ元のシーケンスが変更されたらスレッドセーフじゃない。そして、スレッドセーフ性が完璧に保証されているのがImmutable Collections。という話につながったりつながらなかったり。
Immutable Collections
Immutable Collectionsは実装状況が.NET Framework Blogで随時触れられていて、リリース時のImmutable collections ready for prime timeを読めば、なんなのかっては分かるのではかと。その上で私が今回で割と酸っぱく言いたかったのは、ReadOnly「ではない」ってことです。そして結論はアリキタリに使い分けよう、という話でした。
セッション後の話とかTwitterで、バージョニングされたコレクションって捉えるといいんじゃないの?と意見頂いたのですが、なるほどしっくりきそうです。
スピーカー予定
今後ですが、大阪です!12/14、第3回 LINQ勉強会で発表する予定なので、関西圏の人は是非是非どうぞ。セッションタイトルは「An Internal of LINQ to Objects」を予定しています。これを聞けばLINQ to ObjectsのDeep Diveの部分は全部OK、といった内容にするつもりです。もう初心者向けってこともないので、完全に上級者がターゲットで。