読者です 読者をやめる 読者になる 読者になる

渋谷ほととぎす通信

完全趣味でやってるUnityメモ。説明できないところを説明できるようにするための個人ブログ。昨日の自分より少しでも大きく慣れるように。。。 ※所属団体とは一切関係がありません

LINQとfor文のコスト検証


f:id:esakun:20150730215258g:plain

C#を使う上でLINQは非常に便利な機能なのは言うまでもありません。

しかし、実際のプロダクトへ組み込む際の負荷は知っておくべきということで、改めて調べてみたという記事です。

今回の検証対象は使用頻度が高いフィルタ系関数です。

  • FirstOrDefault
  • Any
  • Where

検証するポイントはUnityProfiler項目で言うと、Time ms(実行時間)GC Allocです。 実行時間は言葉の通りで、高ければ高いほどFPSが低下します。
GC Allocは1フレームにヒープメモリの確保メモリ容量で、数値が高いとGC発生回数が増えてしまい、プロダクトのパフォーマンスを下げる場合があります。

今回の検証では数値を可視化しやすいように配列要素数を10000個1フレーム内の実行回数を100回とします。

検証方法は以前ブログで紹介したやり方です。

任意の処理のCPU負荷を調べる 特定の数行の処理不可を知りたい時が開発中によくあります。 Profiler.BeginSampleとProfiler.EndSampleで処理を挟むと、その間のCPU負荷をProfilerに表示させることが出来ます。 Profilerに表示される名前はBeginSampleの第1引数に文字列で指定します。

Unity Profilerに関する小技集に乗っかる - 渋谷ほととぎす通信

確認場所(for, foreach文など)にProfiler.BeginSampleとProfiler.EndSampleで処理を挟んで数値を確認しています。

FirstOrDefaultの場合
f:id:esakun:20170409221452p:plain

Anyの場合
f:id:esakun:20170409221859p:plain

Whereの場合
f:id:esakun:20170409221742p:plain

for文の場合
f:id:esakun:20170409234956p:plain

これらの結果を以下の表にまとめます。
※追加検証分も含む

処理名 処理時間 GC Alloc
FirstOrDefault 97.52ms 13.3KB
Any 97.52ms 13.3KB
Where 0.05ms 17.2KB
配列のfor文 5.17ms 0KB
IList型のfor文 19.52ms 0KB
List型のfor文 16.65ms 0KB
配列foreach 5.69ms 0KB
List型のforeach 42.15ms 0KB
IList型宣言変数のforeach 43.34ms 3.9KB
IEnumerable型のforeach 101.81ms 10.9KB
  • FirstOrDefaultAnyIEnumerable型のforeach文配列for文配列foreach文約20倍の処理負荷
  • 配列for文配列foreach文が最速
  • LINQ処理は軒並みGC Allocを消費する
  • for文配列・Listのforeach文はGC Allocを消費しない
  • IList型宣言変数のforeachはGCAllocを消費する
  • Listのfor文Listのforeach文より2.5倍早い

まとめ

予想通りパフォーマンスだけで言うと、ベタにfor文で書いた方が早いですし、GC対策にもヒットしますが記述コード量の削減、可読性を考えるとLINQは捨て難いものです。

今回の検証コード自体が、可視化するために非現実で高い負荷をかけています。for文がLINQの20倍早いとはいえ、ループ回数や使用頻度が低ければ、それは全く問題にならないという事になります。

LINQとfor文の使い分けとして今回のような毎フレーム実行する部分でのLINQ使用は控えるべきだと私は考えます。あっという間にGC発生させることに鳴るからです。

高頻度処理には素直にfor文が良いかと。
※配列、Listのforeach文は最終的にfor文に展開されるためforeach記述でも負荷は変わりません

何事も使い所が重要で、どの処理にどれだけのリソースを使うかという情報は把握しておきたいものです。

あわせてどうぞ

www.shibuya24.info

www.shibuya24.info

www.shibuya24.info