Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

[Unity]Linqとforとforeachで負荷計測してみる

More than 3 years have passed since last update.

○経緯

「業務でシーン上にあるオブジェクトの中から最も近いオブジェクトを探す」といった処理を実装した。
しかしその処理は多用されることが想定され出来る限り負荷のかからない処理に置き換えられないかを検討する必要が出てきました。
設計レベルにも問題があるにはありましたが、そもそもしょうもない勘違いで負荷が掛かっているのでは…と初心に戻り最適解が何なのか検証することにしました。

○検証内容

成果物はこちらです。

■比較したパターン

  • 1. Linq を用いて ToList
  • 2. for を用いて if分岐し ListへAdd
  • 3. 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で死ぬこと有りますしそのあたりはおいおいと…。

cyber1496
ゲーム系エンジニア。ここ数年はUnityを使い倒しモバイル向けゲームを作っています。
https://twitter.com/cyber1496
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away