5.0.0)でパフォーマンス向上を果たしたと書きましたが、まだやり残したことがありました。それがボックス化(boxing)の殺害です。ボックス化は単純に言うと、せっかくの値型が箱に入っちゃってGCゴミが発生してGCがーーー、というもの。避けれるなら避けるべし。あ、ちなみに今回の内容は特に別にUnityに限らないふつーのC#の話です。5.1.0リリースしました、アセットストアから落とせます。基本的な内容は以下に解説するボックス化を徹底的に殺害したことによるパフォーマンス向上です。ボックス化とジェネリクスGCって、別に見えてるnewだけで発生するわけでもありません。見えてるものを警戒するのは大事ですが、見えないものを見てないのは片手落ち感が否めない。そんな見えないものの代表例がボックス化です。(object)10;みんな大好きint(ValueType)がobject(ReferenceType)に!これがボックス化の害です。避けたほうが良さそうだ。いえこんなのやらないって?ですよね。Hoge(10);まぁまぁやらないかもしれませんが、まぁまぁやるといえなくもないです。いうかやる時はあります。t1.Equals(t2);}一見何も悪くないのですが、実は悪いです。ようするにEqualsはobjectになった後に比較されてしまうのです。いうわけでボックス化が発生します。ジェネリクスは基本的にボックス化を避けれるのですが、一部のObjectに生えてるメソッド、というかようするにEqualsですが、を触る場合、気をつけないとうっかりしがちです。書いてもボックス化が発生します。書くことで避けられます。EqualityComparer<T>を使うボックス化を避けた比較を行うインターフェイスにIEquatable<T>があります。t1.Equals(t2);}にすればボックス化は避けれる問題なし。あるんですが、これでは不便すぎます(さすがにintとかはIEquatable<T>を実装してはいますが、普通の参照型はほとんど実装していないでしょう)。同じなのかどうかとりあえずチェックしたい、Equalsを普通に呼びたいケースは沢山あります。そこでEqualsを外部から渡せるIEqualityComparer<T>インターフェイスと、デフォルト実装を取得するEqualityComparer<T>.Defaultが使えます。other)を呼んで比較します。5.1.0からは発生しなくなっています。なんで今まで発生していたのかというと、EqualityComparer<T>がiOS/AOTで怪しくてあえて避けてたんですが、5.0.0からAOTサポートはきってIL2CPPのみにしたので無事性能向上を果たせました。UnityとIEquatable<T>Unityにおいては、それだけでメデタシではなく、もう少し話に続きがあります。Unityにおける代表的な値型であるVector2やRectなどは、全て、IEquatable<T>を実装して、いません。==はオーバーライドされているので、素のままで扱って比較している限りは問題ないのですが、ジェネリックの要素として、また、DictionaryのKeyとして使った場合などでもボックス化が発生しています。これが地味に困る話で、UniRxにおいてもObserveEveryValueChangedなどでVector2などが流れてくるたびにボックス化が発生したらちょっとよろしくない。いうものを用意しました。これら代表的なUnityの値型に関しては、専用のEquals/GetHashCodeを実装してあります。より、それらが型から取り出せます。通すようにしているため、極力ボックス化が発生しないようになっています。ラムダ式と見えないnewボックス化、見えないGCゴミの話を書いたので、ついでにもう一つ見えないゴミを発生させるラムダ式について。ラムダ式は実際のところコンパイラ生成の塊みたいなもの、かつ、中身によってかなり生成物が変わってきます。6通りのパターンを用意してみました。6}どんな感じになるか想像できました?では、答え合わせ。簡略化しているので正確にはもう少しこんがらがった機会生成になっていますが、概ねこんな感じになってます。int>(LambdaD));}それぞれ似ているような違うような、ですよね?一つ一つ見ていきましょう。パターン1、パターン2はメソッドを直接突っ込む場合。この場合、実際のところはデリゲートを生成して包んでます。このデリゲートはGCゴミになります。見えないんですが地味にそうなってます。いうわけで、それを回避するには静的メソッドなら静的フィールドに静的コンストラクタででも事前に作ってキャッシュしておく、インスタンスメソッドの場合は、もし使うシーンがループの内側などの場合は外側で作っておくことで、生成は最小限に抑えられるでしょう。パターン3は、恐らく最もよく使うラムダ式の形式で、使う値が全てラムダ式の中だけで完結している場合。この場合、自動的に静的にキャッシュを生成してそれを未来永劫使いまわしてくれるので、非常に効率的です。使う形式が効率的というのは嬉しい、遠慮無くどんどん使おう。パターン4も、まぁよく使う形式、でしょう。ローカル変数をラムダ式内で使っている(キャプチャ)した場合。この場合、普通にクラスがnewされて、そこにラムダ式内部で使われる値を詰め込み、その自動生成のクラスのインスタンスメソッドを呼ぶ形に変換されます。いうわけで、パターン4は見た目は人畜無害ですが、中身はそれなりのゴミ発生器です!いや、まぁたかがクラス一個。あり、されどクラス一個。画面上に大量に配置されるGameObjectのUpdateなどで無自覚に使っていたりすると危なっかしいので、それなりに気を留めておくと精神安定上良いでしょう。パターン5、パターン6は内部でメソッドを使っている場合。ちなみにここではメソッドにしましたが、フィールドやプロパティでも同じ生成結果になります。抱え込む対象がstaticかinstanceかで変わってきて、staticの場合ならキャッシュされるので少しだけ有利です。2015のC#コンパイラによって吐かれるコードであり(Unityの今のmonoもほぼ一緒、のはず、です、確か多分)、将来的にはそれぞれもう少し効率的になるかもしれません(メソッドを直接突っ込む場合のキャッシュとかは手を加える余地がある気がする)。いえ原理を考えたら、外部変数をキャプチャするラムダ式はどうやってもこうなるしかなさそうだったりなので、大筋で変わることはないと思います。まとめ正直なところ今回書いたのは細かい話です!別に気にしすぎてもしょうがないし、というかこんなの細部まで気にして避けながら書くのは不可能です。避けてラムダ式禁止だのLINQ禁止だの言い出すなら、早すぎる最適化の一種で、かなり愚かしい話です。ゲームの中にはひじょーにタイトな部分は存在するはずで、そこで無自覚に使ってしまうのも大きなダメージです。私だってタイトになることが想定されるUpdateループの中でLINQを貫くならやめろバカであり、普通にペタペタとforで書けとは思いますよ。ゼロイチで考えないで、柔軟に対処したいところですねえ。どこに使うべきで、使うべきでないか。その見極めがむつかしいから全面禁止とかって話になるのは実際のところ非常によくわかる!のですが、それこそプロファイラで問題発見されてからでもいいじゃん、ぐらいの牧歌的な考えではいます。いやだって、そんなたかがLINQやラムダ式ぐらいであらゆるところがボトルネックになるわけないぢゃん?そんなのより大事なとこ沢山あるでしょう。それに比べたらLINQを普通に使えることのほうが、UniRxを普通に使えることのほうが100億倍素晴らしい。地味な積み重ねでダメージが出てくるところであり、そして一個一個は地味だったりするから見つけづらくて辛いとかって話もありつつ。errorに見舞われて回避不能で死んでいるので、当面はこの辺が限界です(ほんとuNET絡みのエラーはなんとかして欲しい、理不尽極まりない)。いえ、何かネタがあれば継続してより良くしていきますので、よろしくおねがいします。いえば第一回Unityアセットコンテストでは、セミファイナリスト頂きました。地味なアセットであることを考えると全然上等で、嬉しい話です。