○経緯
「業務でシーン上にあるオブジェクトの中から最も近いオブジェクトを探す」といった処理を実装した。
しかしその処理は多用されることが想定され出来る限り負荷のかからない処理に置き換えられないかを検討する必要が出てきました。
設計レベルにも問題があるにはありましたが、そもそもしょうもない勘違いで負荷が掛かっているのでは…と初心に戻り最適解が何なのか検証することにしました。
○検証内容
成果物はこちらです。
■比較したパターン
-
- Linq を用いて ToList
-
- for を用いて if分岐し ListへAdd
-
- foreach を用いて if分岐し ListへAdd
1.Linq を用いて ToList
var hogeList = (from x in HogeAllList
where /* 条件 */
select x).ToList();
2. for を用いて if分岐し ListへAdd
var hogeList = new List<Hoge>();
for (int i = 0; i < HogeAllList.Count; i++)
{
var x = HogeAllList[i];
if (/* 条件 */)
{
hogeList.Add(x);
}
}
3. foreach を用いて if分岐し ListへAdd
var hogeList = new List<Hoge>();
foreach (var x in HogeAllList)
{
var x = HogeAllList[i];
if (/* 条件 */)
{
hogeList.Add(x);
}
}
この上記3つのパターンでは 2<3<1 の順で処理時間に差ができました。
いやちょい待てよと。1.は別の理由で遅くなってるやろ。
無理にListにしなくても IEnumerable 直接受け取れば良い。
ということで次のパターンを追加し、再検証しました。
4.Linq を用いて IEnumerable を扱う
IEnumerable<Hoge> hoges = (from x in HogeAllList
where /* 条件 */
select x);
そしたら 4< "越えられない壁" <2<3<1 という結果になりました。
そうだよね―Toなんちゃら系重いデスヨネ―
雑にToArrayとかToListしがちだった検証前の自分をひっぱたきたくなりました。
GC使用量も抑えられていてホントなんで素直にこのやり方をせずに
Toなんちゃらしてたのか悔やむばかりです。
※もちろんメリットもありますので設計に合わせて正しくお使いください。
○結論
ToArray, ToList 本当に必要ですか? IEnumerable で良くないですか? と言う視点を持つ。
へんな習慣が身に着いてしまい、より良いコードへの探求心が薄れていたなと感じました。
○追記
Linqの遅延評価を考慮していなかったため
負荷計測のタイミングを見直し再計測を行いました。
(コメント頂きありがとうございました)
以下の foreach をそれぞれの計測タイミングの前に追加しています。
foreach (var hoge in hogeList)
{
}
Debug.LogFormat("Result:{0}", hogeList.Count);
foreach (var hoge in hogeEnum)
{
}
Debug.LogFormat("Result:{0}", hogeEnum.Count());
○再計測の結果
4<2<3<1 という差は変わらないものの、差がわずかに縮まることが確認できました。
ただ、検証を行った状況では低負荷(高速、GC抑制)であることには変わらないようです。
どういったオブジェクトをいくつ生成して回しているか…などはGithubを見て頂ければと思います。
大まかな計測結果は以下の通りでした。
※計測値にバラツキが発生するので同じ処理を複数回回した計測値になっております。
処理 | 処理時間 | GC |
---|---|---|
1. Linq で ListへToList (foreachで中身を確認) | 69msec | 2228224byte |
2. for で ListへAdd (foreachで中身を確認) | 50msec | 2027520byte |
3. foreach で ListへAdd (foreachで中身を確認) | 52msec | 1773568byte |
4. Linq で IEnumerable (foreachで中身を確認) | 16msec | 36864byte |
しかし…コメント頂き計測の妥当性などを思い返してみれば、
この検証はEditor上でしか行っておりませんでした…。
ですのでAndroidやiOSで動かす場合結果が異なる可能性があります。
特にiOSはLinqで死ぬこと有りますしそのあたりはおいおいと…。