RiderのHeap Allocations Viewerについて
コーディング規則をみていると「フィールドにアンスコつけるな!」「privateは冗長だ!」とReSharperにコーディング規則を支配された私には哀しみしかないのですが、ふと「全員がReSharperを使えば問題ないんじゃね」と思ったのでRiderを使ってもらうべく記事書きたいと思います。
というわけで次回のビルドからPublic EAP(になったらいいな)らしいですが、その前にRiderに新しいPluginができました。
ほとんどの人は真ん中のReSharper Unityに目が行くと思いますが、残念ながら私はUnityやってないので今回は一番右の紹介です。(まあHeap Allocations ViewerもどちらかというとUnity向け)
なお私はメモリ等については詳しくないので解説部分とかは軽く読み流してください
Heap Allocations Viewerとは
直訳すると「ヒープ割り当てビューア」(訳:Google先生)
ようするにヒープに割り当てられる処理をわかりやすく表示してくれる拡張機能です。
ヒープに割り当てられるとGCゴミが発生して場合によってはパフォーマンスに大きく影響するのでなるべく表示されなくしたいですね。
例
まあHeap Allocations Viewerを入れるとすぐわかるのですが、表示される例を出していきたいと思います。
new
一番わかりやすいヒープ確保する処理はnew
です。つまり参照型のインスタンス作成。
なおintやdoubleなどの構造体はnewしても表示されません。まあ構造体はスタックなので当たり前ですが。
LINQ
次はみんな大好きLINQです。LINQはメソッドチェインで連続して処理を書けるのでとても見やすく書きやすいですが、実行性能は・・・どうなんでしょう? WhereやSelectなどよく使われるメソッドは最適化がかけられていると風のうわさで耳にしましたがよくわかりません。
Heap Allocations ViewerですとLINQメソッドというだけで表示されます。(ちなみにToList()やFirstOrDefault()などのよく終端で使われるメソッドは表示されません。)
まあLINQは繋げた分だけEnumeratorを撒き散らかしてこれがGCゴミになるとどこかで読んだ気がしますが出典不明なので「LINQはなるべく繋げないほうがいいよー」とだけ覚えておけばいいと思います。
デリゲート
デリゲートというかメソッドグループというかなんというか。ようするにメソッドの引数にメソッドを渡すやつです。 具体的に見てみましょう。
private int InvokeSampleMethod(Func<int,int> func)=>func.Invoke(10); private int OnSampleMethod(int i) => ++i;
というメソッド(例なので全く意味のないメソッドです)に対して、
InvokeSampleMethod(x=>++i); InvokeSampleMethod(OnSampleMethod); InvokeSampleMethod(i=>OnSampleMethod(i));
という3つの書き方をします。メソッドの実行結果としてはほぼ同じですが、この中に1つだけHeap Allocations Viewerで表示されないものがあります。
それは・・・
です。どうやら正解は自己完結しているラムダ式の模様。詳しくは
neue cc - Unityでのボクシングの殺し方、或いはラムダ式における見えないnewの見極め方
をお読みください。
なおメソッドを直接引き渡すやりかたは他にイベントがあります。ということはイベントの購読も自己完結しているラムダ式で書くことで無駄なGCゴミを生成しないと思いきやラムダ式だとイベントの購読を解除できません!残念無念。
クロージャ
デリゲートやったのでついでによくあるパターンとしてこんなのがあると思います。
int i=0; InvokeSampleMethod(x=>x*i);
これはHeap Allocations Viewerだとこうなります。
上の大先生のブログにも書いてありましたが、外の変数をキャプチャする場合、クラスが新しく作られるのでGCゴミが生まれる模様。ただこのパターンはよく見かけるというか、このパターンが便利すぎるので大人しく諦めましょう。しょせんクラス1個分(でもクラス1個分・・・)
見た感じ外の変数を取り込むほどクラスにフィールドが生成されるのでなるべく外の変数を取り込まないようにすると幸せになれるかもしれません。
可変長引数
他にHeap Allocations Viewerが表示される例として可変長引数(params)があります。
例としてDebug.WriteLine(string format, params object[] args)
を書いてみると、
こうなります。
省略可能ですしnew object[]{"hoge","bar"}
などと書かなくてよいので個人的には好きですが、結局配列を生成しちゃうので引数が少ない想定のときはオーバーロードで対応するのがスピードアップへの近道ですかね。(でも配列1個分なら別にゴニョゴニョ)
ボックス化
ところでさっきのスクショを見ていると、引数のところに赤いアンダーラインが引かれてると思います。
わざと赤いラインが引かれるように書いたのですが、objectの引数に構造体を渡すと表示されます。 ようするに構造体(Value Type)からobject(Reference Type)に変更されてせっかくスタックのはずがヒープになってしまってGCゴミガーということです。
ボックス化もパフォーマンスに大きく影響するので(なんとボックス化するのとしないのでは1億回ループを回したときに0.2秒の差がつく!らしい?)できるだけ避けていきたいですね。
他のボックス化の例として、構造体でGetType()を使ったり、自分で定義した構造体でToString()をオーバーライドしないで使ったり、””+1
と書くとボックス化が発生します。
まとめ
以上、Riderの新しいプラグインの解説(?)でした。このプラグインを使ったからといって別にコーディングが楽になったりしないし、表示されるのを気にしてコードを複雑にするのは個人的にアンチパターンだと思います。ただ、チームでやっている場合、誰かがこれを使っていれば、もしかしたら無駄な処理に気付けるかもしれません。なのでMacでC#やっている人は積極的に使ってみてほしいですね!Riderも一緒に!!