はじめに
Unityを触っていて、なんだかArray.Reverseが遅く、Enumerable.Reverseにしてみたら速くなったという不思議な現象に出会いました。
https://docs.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.reverse?view=net-6.0
https://docs.microsoft.com/ja-jp/dotnet/api/system.array.reverse?view=net-6.0
環境
- Unity2020.3.32f1
- macOS Monterey
私の認識だと一般的にArray.Reverseの方がEnumerable.Reverseよりも処理速度が速いと思っていたのですが、案外そうではないんですかね。
というわけで調べてみました。
Unity2020での検証
環境
- Unity2020.3.32f1
- .NET Standard2.0
- Mono
- macOS Monterey
private void Start() { var array = Enumerable.Range(0, 100000000) .ToArray(); var sw = new Stopwatch(); // Array.Reverse sw.Start(); Array.Reverse(array); sw.Stop(); Debug.Log($"Array.Reverse : {sw.ElapsedMilliseconds}ms"); // 同じ実験対象にするために順番を戻しておく array = array.Reverse().ToArray(); // Enumerable.Reverse sw.Reset(); sw.Start(); array = array.Reverse().ToArray(); sw.Stop(); Debug.Log($"Enumerable.Reverse : {sw.ElapsedMilliseconds}ms"); }
| メソッド | 処理速度 |
|---|---|
| Array.Reverse | 100952ms |
| Enumerable.Reverse | 515ms |
Array.Reverseがやたら遅いですね。ちなみに実行する順番を変えたりしても同じ結果になりました。
.Net Framework v4.8のConsoleアプリケーション
今度はUnityを利用せずに、Riderを利用してConsoleアプリを作成して計測してみます。
環境
- macOS Monterey
- .Net Framework v4.8
- Rider2020.3.4
public static void Main(string[] args) { var array = Enumerable.Range(0, 100000000) .ToArray(); var sw = new Stopwatch(); // Array.Reverse sw.Start(); Array.Reverse(array); sw.Stop(); Console.WriteLine($"Array.Reverse : {sw.ElapsedMilliseconds}ms"); // 同じ実験対象にするために順番を戻しておく array = array.Reverse().ToArray(); // Enumerable.Reverse sw.Reset(); sw.Start(); array = array.Reverse().ToArray(); sw.Stop(); Console.WriteLine($"Enumerable.Reverse : {sw.ElapsedMilliseconds}ms"); }
| メソッド | 処理速度 |
|---|---|
| Array.Reverse | 63058ms |
| Enumerable.Reverse | 534ms |
Unityを利用しなくてもArray.Reverseが極端に遅いですね。うーん。
.Net Core 3.1のコンソールアプリケーション
なんかこのまま終わるのが癪なので、.Net Coreを利用し、Visual Studioでやってみます。
環境
- macOS Monterey
- .Net Core 3.1
- VisualStudio for Mac v17.0.5
| メソッド | 処理速度 |
|---|---|
| Array.Reverse | 53ms |
| Enumerable.Reverse | 288ms |
まさかの結果になってしまいました。圧倒的にArray.Reverseの方が早いですね。
.Net 5.0のコンソールアプリケーション
次は.NET 5.0。
環境
- macOS Monterey
- .Net 5.0
- VisualStudio for Mac v17.0.5
| メソッド | 処理速度 |
|---|---|
| Array.Reverse | 59ms |
| Enumerable.Reverse | 351ms |
これもArray.Reverseの勝利です。
Unity2021での検証
環境
- Unity2021.3.0f1
- .NET Standard2.1
- macOS Monterey
| メソッド | 処理速度 |
|---|---|
| Array.Reverse | 63ms |
| Enumerable.Reverse | 443ms |
普通にArray.Reverseが勝ちました。2021で.Net Standard2.1に対応したのでこれが原因なのでしょうか。
Unity2019
環境
- Unity2019.4.0f1
- .NET Standard2.0
- Mono
- macOS Monterey
| メソッド | 処理速度 |
|---|---|
| Array.Reverse | 128206ms |
| Enumerable.Reverse | 484ms |
- Unity2019.4.0f1
- .NET 4.x
- Mono
- macOS Monterey
| メソッド | 処理速度 |
|---|---|
| Array.Reverse | 125947ms |
| Enumerable.Reverse | 440ms |
ひとこと
Unity2021以上か未満かで速度にかなりの違いがでてきていました。
おそらく.NETのバージョンによる違い?なのかなとも思いますが、他の要因なのかもしれません。
Array.ReverseはO(n)なはずですし、なにかが悪さしている気がします。
何か知っている方がいましたらコメント等で教えていただけますと幸いです。
追記
.NET Fxでも元々 Mono が遅くて(純正はネイティブ側対応が入っているからか大抵速い)、.NET Standard 2.1 / .NET Core 2.0 以降から入った Array.Reverse<T> を含む新しい実装が Unity / Mono側にも入ったので改善されたということっぽい。 https://t.co/GJVa6LjmZA
— Mayuki Sawatari (@mayuki) July 28, 2022