LoginSignup
4

More than 5 years have passed since last update.

【UnityC#】forループとforeachループの処理時間を比較してみた

Last updated at Posted at 2018-04-24

はじめに

Unityでのforループとforeachループの配列アクセスの処理時間を計測・比較してみました。

image.png

環境

Excel2016
Windows 10
Unity 2017.3.0f3

PCスペック

メモリ 32GB
CPU Intel(R) Core(TM) i7-6800K CPU @ 3.40GHz

今回やったこと

手順1. forループとforeachループの処理時間を500回計測し、CSVに保存
手順2. CSVから処理時間のヒストグラムを作成
手順3. ヒストグラムからグラフを作成・比較

計測コード

今回は以下の二つのコードで処理時間を計測します。

forループを使った配列走査
int x;
for (int i = 0; i < N; i++)
{
    x = a[i];
}
foreachループを使った配列アクセス
int x;
foreach (var item in a)
{
    x = item;
}

今回は配列aの長さを10万としました(N = 100000)

計測

CSVの作成を行うクラス FileManager

FileManager.cs
using UnityEngine;
using System.IO;
using System;

public class FileManager
{
    // ファイル書き出し
    public static void Save(string text)
    {
#if UNITY_EDITOR
        var now = System.DateTime.Now;
        var datetime = string.Format("{0}{1:D2}{2:D2}_{3:D2}{4:D2}{5:D2}", now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second);
        var path = Application.dataPath + "/" + string.Format("Measure{0}.csv", datetime);

        System.IO.StreamWriter sw = new System.IO.StreamWriter(path, false, System.Text.Encoding.GetEncoding("shift_jis"));

        sw.Write(text);
        sw.Flush();
        sw.Close();

        UnityEditor.AssetDatabase.Refresh();
#endif
    }
}

処理時間を計測するクラス MeasureTest

計測を行うメインのクラスはこちらです。

MeasureTest.cs
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using UnityEngine;

/// <summary>
/// 処理負荷の計測テスト
/// </summary>
public class MeasureTest : MonoBehaviour
{
    const int COUNT = 500; // 計測回数
    const int N = 100000; // 配列の長さ
    int[] a = new int[N]; // 何らかの配列


    void Start()
    {
        string text = "";
        text += string.Format("計測日時 : {0}\n", System.DateTime.Now);
        text += string.Format("計測回数 : {0}\n", COUNT);
        text += string.Format("配列アクセス回数 : {0}\n", N);
        text += string.Format("For(ms),ForEach(ms)\n");

        for (int i = 0; i < COUNT; i++)
        {
            double forSec = TestFor();
            double foreachSec = TestForEach();

            text += string.Format("{0},{1}\n", forSec, foreachSec);
        }

        // 計測結果は外部ファイルへ
        FileManager.Save(text);
    }

    // forループの処理時間の計測
    double TestFor()
    {
        int x;
        var sw = new Stopwatch();
        sw.Start();
        for (int i = 0; i < N; i++)
        {
            x = a[i];
        }
        sw.Stop();

        return 1000.0 * (double)sw.ElapsedTicks / (double)Stopwatch.Frequency; // 処理時間
    }

    // foreachの処理時間の計測
    double TestForEach()
    {
        int x;
        var sw = new Stopwatch();
        sw.Start();
        foreach (var item in a)
        {
            x = item;
        }
        sw.Stop();

        return 1000.0 * (double)sw.ElapsedTicks / (double)Stopwatch.Frequency; // 処理時間
    }
}

計測する

上記のMeasureTestクラスをシーン内の適当なGameObjectにアタッチしてゲームを再生します。

ゲームを再生するとMeasure_(日付).csvという名前のファイルが作成されます。
image.png

CSVの中身

CSVの中身はこのような感じになっています。

手順2. ヒストグラムを作成する

続いては、このCSVからヒストグラムを作っていきたいと思います。

CSVの編集 : データ区間の定義

まずは、データ区間となる数値を入力していきます。

CSVを以下のように編集し、データ区間を定義します。
image.png

image.png

ヒストグラムの作成

参考 : エクセルでヒストグラムを作成する方法。正しいグラフの分析とは?

forループの処理時間のヒストグラム作成

まずは"For(ms)"と"データ区間"に関してのヒストグラムを作成します。

ヒストグラム設定
image.png

作成直後
image.png

foreachループの処理時間のヒストグラム作成

次に"ForEach(ms)"と"データ区間"に関してのヒストグラムを作成します。
image.png

作成直後
image.png

CSVを整形する

余分な行が入っていたり、名前があいまいなので見やすいように整形します。

E列のカラム名 "頻度" -> "for(ms)"
G列のカラム名 "頻度" -> "foreach(ms)"
D列とF列を削除

image.png

手順3. ヒストグラムからグラフを作成する

数値を選択する

"データ区間"、"for(ms)"、"foreach(ms)"の数値を選択します。
image.png

グラフの作成

おすすめグラフから"集合縦棒"を選択します。

作成直後

グラフの整理

分かりやすいように整理します
image.png

グラフの考察

foreachは若干重い (forのほうがちょっと軽い)
・グラフのピークが両方とも左側へ寄っている。 どういうことなんだろう?
・forの処理時間 > foreachの処理時間となってしまうケースも低確率で起こり得る。

平均値も比較

処理時間の平均値も比較してみました。

forループを処理時間の平均値は 0.7358128(ms)
foreachループの処理時間の平均値は 0.8249308(ms)

0.8249308 / 0.7358128 ≒ 1.121115044

foreach の処理時間は for と比較して 12%高くなるようです。

結論

foreach は for より ちょっと重い

追記: List<T>の処理時間

ちょうどよい機会なので、forやforeachでList<T>にアクセスした場合の処理負荷時間も計測してみました。

N = 1000の場合

リストや配列の要素数 : 1000
計測回数 : 500

ヒストグラム
image.png

平均値などの比較
image.png

foreachでListにアクセスした場合、forループ配列アクセスの約5倍の処理時間がかかるようです (要素数が1000の場合)

N = 10万の場合

リストや配列の要素数 : 10万
計測回数 : 500

ヒストグラム
image.png

平均値などの比較
image.png

foreachでListにアクセスした場合、forループ配列アクセスの約5倍の処理時間がかかるみたいです (要素数が10万の場合)

List<T>の計測ソースコード

List<T>の計測に使用したコードは以下になります。

MeasureFor.cs
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using UnityEngine;

/// <summary>
/// forループやforeachループの処理負荷の計測テスト
/// </summary>
public class MeasureFor : MonoBehaviour
{
    const int COUNT = 500; // 計測回数
    const int N = 1000; // 配列の長さ
    int[] array = new int[N]; // 何かの配列
    List<int> list = new int[N].ToList(); // リスト

    void Start()
    {
        string text = "";
        text += string.Format("計測日時 : {0}\n", System.DateTime.Now);
        text += string.Format("Unityバージョン : {0}\n", Application.unityVersion);
        text += string.Format("計測回数 : {0}\n", COUNT);
        text += string.Format("配列アクセス回数 : {0}\n", N);
        text += string.Format("For-Array(ms),ForEach-Array(ms),For-List(ms),ForEach-List(ms)\n");

        for (int i = 0; i < COUNT; i++)
        {

            text += string.Format("{0},{1},{2},{3}\n", TestForArray(), TestForEachArray(), TestForList(), TestForEachList());
        }

        // 計測結果は外部ファイルへ
        FileManager.Save(text);
    }

    // forループの処理時間の計測
    double TestForArray()
    {
        int x;
        var sw = new Stopwatch();
        sw.Start();
        for (int i = 0; i < N; i++)
        {
            x = array[i];
        }
        sw.Stop();

        return 1000.0 * (double)sw.ElapsedTicks / (double)Stopwatch.Frequency; // 処理時間
    }

    // foreachの処理時間の計測
    double TestForEachArray()
    {
        int x;
        var sw = new Stopwatch();
        sw.Start();
        foreach (var item in array)
        {
            x = item;
        }
        sw.Stop();

        return 1000.0 * (double)sw.ElapsedTicks / (double)Stopwatch.Frequency; // 処理時間
    }

    // forループの処理時間の計測
    double TestForList()
    {
        int x;
        var sw = new Stopwatch();
        sw.Start();
        for (int i = 0; i < N; i++)
        {
            x = list[i];
        }
        sw.Stop();

        return 1000.0 * (double)sw.ElapsedTicks / (double)Stopwatch.Frequency; // 処理時間
    }

    // foreachの処理時間の計測
    double TestForEachList()
    {
        int x;
        var sw = new Stopwatch();
        sw.Start();
        foreach (var item in list)
        {
            x = item;
        }
        sw.Stop();

        return 1000.0 * (double)sw.ElapsedTicks / (double)Stopwatch.Frequency; // 処理時間
    }
}

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4