この記事では .Net Framework と C# の、多次元配列、ジャグ配列、List<T> について記載しています。
多次元配列、ジャグ配列、List<T> の中で、
アクセス(get/set)スピードが最も優れているのは、ジャグ配列です。
速さと使いやすさを両立できるのは List<T> です。
エクセルのような、縦と横の2次元配列で表現できそうなデータを管理しよう!となった場合、C# にはそのまんま、多次元配列という仕組みがあるので、多次元配列で実装しようと考えます。
//多次元(ここでは2次元)配列の宣言 string[,] cells = new string[100,100]; //51行目、21桁目のセルに hogehoge を代入 cells[50,20] = "hogehoge";
直感的で分かりやすいのですが、多次元配列には重大な欠陥があります。
問題点は、Unity マニュアルの最適化のページに記載されています。
すごく簡単に言うと、
死ぬほど遅い!
じゃあ、Unity 以外の .Net のバージョンでも、同じ結果になるのか?は、実際に速度を測ってみないと分かりません。
同じバージョンの .Net を使っても、Win64 と IOS で同じ結果になるのか?も分かりません。
ということで、
.Net Framework 4.8.3
C# 5
Windows 10(64bit)
という条件で速度比較をしてみました。
結果は
速い ジャグ配列 > List<T> > 多次元配列 遅い
Unity に限らず、多次元配列が遅いことは変わりませんでした。
集計データを Google Spreadsheet にまとめているので、興味がある方はご覧ください。
Windows 10 以外の環境がないので、他の OS でも同じ結果になるのかは分かりません。
アクセススピードが最も優れている、ジャグ配列は以下のように使います。
//ジャグ配列の宣言 string[][] cells = new string[][]{ new string[3], new string[3] }; //1行目2桁目に hogehoge を代入 cells[0][1] = "hogehoge";
ジャグ配列に限らず、配列の問題点は、配列のサイズが変わる場合、その処理を作る手間です。
配列のサイズを変える場合、以下の処理を作る必要があります。
・新しいサイズの配列を作成
・サイズが増える場合
元の配列の全ての要素を新しい配列にコピーして、増えた分を初期値で埋める。
・サイズが減る場合
元の配列から新しいサイズ分の要素を新しい配列にコピーする。
単純にコピーに時間がかかります。
この時間がバカにならないので、ゲームだとなんでもかんでも上限をつけて、サイズが変わらないようにするのが常套手段です。
サイズが変わらないようにする理由は他にもありますが、長くなるのでここでは割愛します。
サイズが変わってもそこそこのパフォーマンスを出せるようにするには、List<T> を使います。
//二次元目の要素を追加 List<List<int>> cells = new List<List<int>>(); //一次元目の要素を追加 cells.Add(new List<int>()); //[0]に二次元目の要素を追加しつつ、0 を代入 //cells[0][0] = 0 になった cells[0].Add(0); //一次元目の要素を追加 //cells[1] が追加される cells.Add(new List<int>()); cells[1].Add(0); //[1]に[0]を追加しつつ 0 を代入 cells[1].Add(1); //[1]に[1]を追加しつつ 1 を代入
ジャグ配列よりも、ちょっと使い方が分かりにくいです。
List<T> は、配列のように最初に決まったサイズを確保するわけではなく、必要に応じて Add() で要素を追加していくものです。
配列と違って、サイズを変更しても要素の全コピーは発生しません。
アクセススピードはジャグ配列よりも若干劣りますが、要素の追加と削除のしやすさを考えると、これくらいのロスは許せるかな?というレベルです。
むしろ、リスト構造でよくここまでのパフォーマンスを出せているなぁ…と惚れ惚れするレベルです。
配列のように使いたい場合、インスタンスを生成したあと、以下のように初期化します。
List<List<int>> cells = new List<List<int>>(); //[100][100]の要素を確保し、0 で初期化 for (int i = 0; i < 100; i++) cells.Add(new List<int>()); //一次元目を確保 for (int i = 0; i < 100; i++) { for (int j = 0; j < 100; j++) cells[i].Add(0); //二次元目を確保しながら 0 を代入 }