vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

【C#】値型のローカル変数はどこに置かれるか

はじめに

ふと気になったので。

string など参照型のローカル変数を作った場合、そのデータはメモリのヒープ領域に置かれ、使われなくなるとガーベジコレクションによって回収されます。

では値型の場合は?

ということで調べてみることにしました。

準備

今回の確認に使用するのは、毎度おなじみ Unity の Profiler です。

最初は Visual Studio の診断ツールを使っていたのですが、ヒープ領域の確認は簡単にできたもののスタック領域の確認方法がわからなかったので変更しました。
(こちらはおいおい再挑戦したいと思っています)

環境

Unity2018.2.8f1

ベースのコード

検証用のコードです。

後で触れますが、メンバー変数について調べるためにクラスのインスタンス生成時のプロファイルも取るようにしています。

MainController.cs

using UnityEngine;
using UnityEngine.Profiling;
using UnityEngine.UI;

public class MainController : MonoBehaviour {
    public Button PlayButton;
    private void Start () {
        // クラスのインスタンス生成時の計測.
        Profiler.BeginSample("SamplingProfile1");
        CustomClass c = new CustomClass();
        Profiler.EndSample();
        
        PlayButton.onClick.AddListener(() => {
            // 計測開始.
            Profiler.BeginSample("SamplingProfile");
            c.Say();
            // 計測終了.
            Profiler.EndSample();
        });
    }   
}

こちらに変数を入れたり消したりして計測をしてみます。

CustomClass.cs

public class CustomClass {
    public void Say() {
    }
}

ローカル変数

さっそく下記の3条件で計測してみます。

  1. ローカル変数なし
  2. 値型( float )のローカル変数あり
  3. 参照型( string )のローカル変数あり

ローカル変数なし

CustomClass.cs

public class CustomClass {
    public void Say() {
    }
}

値型( float )のローカル変数あり

CustomClass.cs

public class CustomClass {
    public void Say() {
        float message = 1000000000000;
        float message2 = message * 2000000000;
        float message3 = message2 - 30000000000000000;
    }
}

参照型( string )のローカル変数あり

CustomClass.cs

public class CustomClass {
    public void Say() {
      string message = "1000000000000";
      string message2 = "message * 2000000000";
      string message3 = "message2 - 30000000000000000";
    }
}

結果

f:id:mslGt:20180930093315j:plain

値型のローカル変数ありの場合を見ると、空の場合と比較して Total, Self の割合は増えていますが、 GC Alloc は 0 のままです。

参照型のローカル変数ありの場合では GC Alloc の値が増えており、参照型のローカル変数はヒープ領域に置かれていることが見られます。

メンバー変数

ではメンバー変数の場合はどうでしょうか。

下記によると、参照型であるクラスのメンバー変数の場合は、値型であってもヒープ領域に置かれるようです。

ja.stackoverflow.com

これを確かめてみます。

  1. メンバー変数なし
  2. 値型( float )のメンバー変数あり
  3. 参照型( string )のメンバー変数あり

メンバー変数なし

CustomClass.cs

public class CustomClass {
    public void Say() {
    }
}

値型( float )のメンバー変数あり

CustomClass.cs

public class CustomClass {
    
  private float message = 1000000000000;
    private float message2 = 2000000000;
    private float message3 = -30000000000000000;
    
  public void Say() {
    }
}

参照型( string )のメンバー変数あり

CustomClass.cs

public class CustomClass {
    
  private string message = "1000000000000";
    private string message2 = "message * 2000000000";
    private string message3 = "message2 - 30000000000000000";
    
  public void Say() {       
    }
}

結果

f:id:mslGt:20180930093119j:plain

値型( float )の場合も GC Alloc の量が増えていることが見られます。

ということで、クラスのメンバー変数の場合は、値型の場合であってもヒープ領域に置かれるようです。

おわりに

データがスタック領域に置かれるのかヒープ領域に置かれるのかは、ガーベジコレクションの対象になるかどうか、といった面で重要になります(なる場合があります)。

まぁググれば情報は見つかるわけなのですが、実際に自分で見てみる、というのも楽しいものですね。

一点気になる点として、値型の変数として Decimal を使おうとしていたのですが、ローカル変数の場合でも GC Alloc が増えました。

Decimal が struct であること、Visual Studio の診断ツールではヒープ領域の量が増えないことから値型であることには間違いないと思うのですが。

こちらも何かわかったら追記、または別途書くことにしたいと思います。

参照