前回は、データベースの SlowQueryについてご紹介しました。
今回は、C#のList系パフォーマンス比較についてご紹介します。
どの言語でプログラムを書いても、必ずと言っていいほど使用することになるものの一つに配列があります。
モバイルアプリを作成するうえでも、よくお世話になります。
現在、携わっているプロジェクトのある機能を作成した際に、動作の面で思うようにパフォーマンスが出せずに苦戦しています。
その機能では、比較的多くのオブジェクトを毎フレーム監視する必要があるため、それらのオブジェクトを配列で管理しUpdate関数の中でループする形となっています。
ループ以上に重い処理を実施している箇所も多くあるため、パフォーマンスが出ていない原因の1つとして考えているわけではありませんが
フレーム処理の度に配列をループした際、使用する型によってどの程度パフォーマンスに差が出るのか気になったので、簡単に程度に時間を計ってみました。
このプロジェクトでは、Unityを使用してアプリ開発を行っているため、使用言語はC#です。
C#で配列を扱う場合、いくつか選択肢があり大きく分けで以下になるかと思います。
・配列 (int[]やobject[]等の固定長配列
・ArrayList
・List
・HashSet
上記の内、ArrayListから下は、可変長配列となっておりforeachを使用することが出来るため
配列のみfor文、ArrayList以降はfor・foreach文を使用した際の時間を計りました。
測定内容としては、簡単な自作クラスを用意し、そのクラスのインスタンスを100~100000個生成し配列に詰め、全要素をループして回すというものです。
□自作クラス
public class Obj : IComparable, IComparable<Obj> { public int intKey = 0; public string strKey = ""; public int CompareTo(object other) { return intKey.CompareTo(((Obj)other).intKey); } public int CompareTo(Obj other) { return intKey.CompareTo(other.intKey); } }
□配列準備
Obj[] array = null; ArrayList arrayList = new ArrayList(); List<Obj> list = new List<Obj>(); HashSet<Obj> hash = new HashSet<Obj>(); Dictionary<int, Obj> intDictionary = new Dictionary<int, Obj>(); Dictionary<string, Obj> strDictionary = new Dictionary<string, Obj>(); for (int i = 0; i < 100; i++) { Obj obj = new Obj(); obj.intKey = i; obj.strKey = i.ToString(); arrayList.Add(obj); list.Add(obj); hash.Add(obj); intDictionary.Add(obj.intKey, obj); strDictionary.Add(obj.strKey, obj); } array = list.ToArray();
1000以上の要素を配列に詰めて使用することは、ほとんどないと思いますが
ループ処理を実行する回数が多ければ、結果的には同じ回数の処理をすることになるため、今回はこの形としました。
実行結果は、下記です。
HashSetのループが他と比べてとても遅いようです。
HashSetは重複したデータを許可しないクラスであるため、他の配列型クラスより複雑な処理を内部で行っているのかもしれません。
私個人としては、重複しないデータを管理する場合は、最初からHashSet自体は使わずに、一旦List
最終的にデータを整理するためにHashSetへ変換といった形で使用することが多いため、あまり影響は大きくないかなといった印象です。
ArrayListもあえて使用するといったことは少ないのではないでしょうか。
多くの方は、固定長配列もしくはジェネリックのList
やはり一番最初に目につくのは、List
foreach文は記述する内容がfor文に比べて少なく、シンプルな書き方をすることができて便利なので、よく使用しますが
100回ループの結果を見るだけでも、1回の実行で0.05msの差があります。
数回の実行で済む処理であれば、さほど気にはなりませんが、頻繁にループ処理が必要な場合は、for文を使用したほうが明らかにパフォーマンスが向上しそうです。
そして、上記以上に注目しなくてはいけないのが、配列とList
可変長で配列にはない機能がある分、ループといった基本的な動作の時にも、いろいろなオーバーヘッドが発生しているあるということですね。
自由に値の追加・削除が出来ることから、あまり深く考えずにList
作成する処理に最適なものはどちらなのかということを判断し、適切に選択していくことによって、アプリのパフォーマンスも大きく変わってきます。
一つ一つで見たときは小さくても、アプリの規模が大きいとそれに比例して差が開いていき、最終的にそこがボトルネックになる可能性も十分ありえます。
いろいろなアルゴリズム・ロジックや設計知識を持つことももちろん重要ですが
それらと同等もしくはそれ以上に、言語標準機能の動きを把握することがエンジニアとしては重要なんだと改めて実感しました。
以上で、C#のList系パフォーマンス比較のご紹介は終了となります。
最後までご覧いただきありがとうございました。
おまけでソート実行時間も図ったので、載せておきます。
ソートに関していえば、配列よりもList
弊社では全国各地の請負い(ご自宅)で作業協力頂ける、フリーランスエンジニアの方を常時探しております。
ご興味ある方は、お気軽にお問い合わせ下さい。