Monadic Critical Path

仕事のアプリ、長いこと測定されそこねていたが重要な速度上の指標が下がっているから直せという。速かった頃のバイナリを持ってきてトレースを比べるが・・・わからん。これはリグレッションした箇所を見つけて直すより真面目に速くするほうが良さそう。

このアプリは色々マルチスレッドにして速度を稼いでいる。そしてアプリ内で CPU インテンシブな部分(View の構築など)とシステムのサービスを待つのが遅い部分がある。当然後者は非同期に待つ。結果としてぱっとトレースをみただけだと critical path がわからない。

仕方ないのでコードを睨んでいるのだが、むー。

自分は critical path を依存関係のグラフとして理解しようとしている。最終的なオブジェクト A を得るには オブジェクト B と C が必要で、C を作るには D が必要、だからクリティカルパスは B->A か D->C->A ... みたいな。グラフの arc に所要時間を割り振っていく。

このモデリングをコードにマップする方法がいまいちはっきりしない。

まず依存関係のノードがコードの上のオブジェクトとして現れるとは限らない。たとえば最後の測定地点が、とあるシステムからのコールバックが呼ばれたところであるとする。コールバックに引数はない。だから目に見えるオブジェクトがあるわけではない。仕方ないので概念上の Readiness object みたいのを考える。

その一方、コード上存在感がある割に依存関係のノードにならないクラスが多い。具体的には Creator やら Initializer やらの Verb-er class. Java のイディオム的にオブジェクトなだけで、こいつらは事実上の関数である。しか本物の関数ではないせいで、依存関係の in と out をわかりにくくしている。

非同期のわかりにくさという意味では、関数やコンストラクタの引数に Future 的なオブジェクトを渡されるのも辛い。戻り値だけにしてくれ…。

わかりにくさの理由は2つ。一つは Future (ListenableFuture) が入力なのか出力なのかわからないこと。まあ ListenableFuture なら入力だし SettableFuture なら出力なのだが, ownership の行方がすぐわからなくなる。

もう一つは、要するに pure function と IO function みたいのが混ざってしまう、ということだろうなあ。Future にアクセスする関数やオブジェクトというのは非同期のフロー (IO) に参加している。一方で binder の遅いブロッキングコールを読んだりもする (pure). もうわけがわからない。やはりロジックはなるべく pure な感じに書いて、一番外側で IO 的なものを組み合わせる作りにしてほしいなあ。しかし Java というのは文化的にそういう区別をがんばっていないのでどうにもならないのだった。Rx 世代のコードベースで働きたいものであることよ・・・。

ただ Rx 的なコードの性能分析がどうあるべきなのか、と聞かれてもぱっと答えることはできないな。IO の中の時間と自分の pure な部分の時間を、どうにかして可視化するのだろうけれど・・・。


コード中心の分析がそもそも筋悪な気もする。手元にはいちおう Systrace のダンプがあり、これは時間とスレッドの関係についての雛形になりうる。こいつを画像にキャプチャし、その上に依存関係のフローを注釈として書き込んでいけばいいかもしれない。それなら可視化のベースラインを捏造しなくてよくなるよね。こうしてみるかな。

本来なら Systrace の API が非同期のフローをサポートして、手書きで注釈を後付する必要なんてのはない方がよいんだけど。そこは多くを期待するべからずということで。