LINQのパフォーマンスは遅いのか [Unity]
興味本位でLINQのパフォーマンスについて調べてみた.
LINQ とは
こちらの記事に詳しく書かれている.
こちらな記事でも書かれているんですが,LINQはデメリットとして,パフォーマンス低下を招きやすいというのがあります. 実際に定量的に調査してみたくなりました.
Unity 2018.1 かつ IL2CPP
JacksonDunstan氏が行ったパフォーマンス検証.
最もよく使われるLINQである,Where
とSelect
で以下の3つのパターンで比較.
- manual バージョン (whereとselectの機能を手動で)
- normal バージョン (whereとselectの機能をデリゲートで)
- LINQ バージョン (whereとselectの機能をLINQで)
この時の検証環境は以下の通りだった.
- Unity 2018.1.0f2
- IL2CPP
検証コード
少しコードがおかしい部分があったので,コメント加筆しつつ修正.
using System.Collections; using System.Collections.Generic; using UnityEngine; using System; using System.Linq; using System.Diagnostics; public class LinqPerformanceTest : MonoBehaviour { private string report; void Start() { int[] array = new int[1024]; // 10000回繰り返す const int numIterations = 10000; Stopwatch stopwatch = new Stopwatch(); // デリゲート宣言 Func<int, bool> checker = val => val == 1; List<int> found = null; // ManualバージョンでWhereの時間を検証 stopwatch.Reset(); stopwatch.Start(); for (int it = 0; it < numIterations; ++it) { int len = array.Length; found = new List<int>(len); for (int i = 0; i < len; ++i) { int elem = array[i]; if (elem == 1) { found.Add(elem); } } } long whereManualTime = stopwatch.ElapsedMilliseconds; // NormalバージョンでWhereの時間を検証 stopwatch.Reset(); stopwatch.Start(); for (int it = 0; it < numIterations; ++it) { int len = array.Length; found = new List<int>(len); for (int i = 0; i < len; ++i) { int elem = array[i]; if (checker(elem)) { found.Add(elem); } } } long whereNormalTime = stopwatch.ElapsedMilliseconds; // LinqバージョンでWhereの時間を検証 stopwatch.Reset(); stopwatch.Start(); for (int it = 0; it < numIterations; ++it) { array.Where(checker).ToList(); } long whereLINQTime = stopwatch.ElapsedMilliseconds; // デリゲート宣言 Func<int, int> transformer = val => val * 2; List<int> transformed = null; // manual バージョンでWhereの時間を検証 stopwatch.Reset(); stopwatch.Start(); for (int it = 0; it < numIterations; ++it) { int len = array.Length; transformed = new List<int>(len); for (int i = 0; i < len; ++i) { transformed.Add(array[i] * 2); } } long selectManualTime = stopwatch.ElapsedMilliseconds; // normal バージョンでWhereの時間を検証 stopwatch.Reset(); stopwatch.Start(); for (int it = 0; it < numIterations; ++it) { int len = array.Length; transformed = new List<int>(len); for (int i = 0; i < len; ++i) { transformed.Add(transformer(array[i])); } } long selectNormalTime = stopwatch.ElapsedMilliseconds; // LINQ バージョンでWhereの時間を検証 stopwatch.Reset(); stopwatch.Start(); for (int it = 0; it < numIterations; ++it) { array.Select(transformer).ToList(); } long selectLINQTime = stopwatch.ElapsedMilliseconds; report = "Test,Manual Time,Normal Time,LINQ Time\n" + $"Where,{whereManualTime},{whereNormalTime},{whereLINQTime}\n" + $"Select,{selectManualTime},{selectNormalTime},{selectLINQTime}"; } // 結果の表示 void OnGUI() { GUI.TextArea(new Rect(0, 0, Screen.width, Screen.height), report); } }
結果
Unity 2018.1 の時はこんな感じでした.
Unity 5 時代で Scripting Backend が Monoだったときは,LINQは劇的に遅かったとあります. こちらの記事の棒グラフを見ると頭が痛くなるほどの遅さであることがわかります. jacksondunstan.com
Scripting BackEnd を IL2CPPにすることで劇的に早くなりました.
Unity 2019.3 かつ IL2CPP
では Unity 2019.3 ではどうか.検証してみた.
検証コードは上記と同じで,環境は Unity 2019.3.11f1 で IL2CPP.
結果
Whereは LINQ が一番早くなっている. Select は Normal Time より早くなった.
実はLINQはUnity2019.3から早くなった説…? というかmanualのタイム遅くなってないか…?
一応10回同様に測定してみましたが,誤差は少々あるもののだいたい同じ数値に収まりました.
終わりに
私の環境間違いの可能性もあるけど,少し目を疑っている. もう少し環境を見直して,他のバージョンでも確かめてみようと思います.
LINQで書いた方が可読性高いし,認知負荷が低いからLINQで書けたら書きたいんだよな.
メソッドチェーン大好き人間としてはな….