C++のとき構造体のデータのアレンジメントをよく注意しなればないですが、C#はほとんどは自動管理でやります。でも本当に何もしなくていいですか?これについて確認します。
最初は簡単な構造体を作ります。
struct MyStruct
{
public byte a; //1byte
public int b; //4byte
public short c; //2byte
public byte d; //1byte
}
Marshal.SizeOfを利用してメモリサイズを確認したら12 bytesを返します。つまりC#は自動的に4 byteのブロックを分けています。
現在のレイアウトは下のようにアレンジメントしています。こうする見ると、この中にはすでに4 bytesの無駄スペースを発生しました。
[byte][..][..][..]
[int][int][int][int]
[short][short][byte][..]
今ブロックの長さは一番長いのintのサイズと一致ですから、intをdoubleに変わると24 Bytesになるはず。
早速確認すると、やっぱり24 bytesになりました。
struct MyStruct
{
public byte a; //1byte
public double b; //8byte
public short c; //2byte
public byte d; //1byte
}
メモリレイアウトはこうになりました。無駄なメモリも12 bytesになりました。50%のスペースが使わないからなんかもったいないです。では、手動的にデータの順序でレイアウトを変わってみます。
[byte][..][..][..][..][..][..][..]
[double][double][double][double][double][double][double][double]
[short][short][byte][..][..][..][..][..]
はじめてのメモリレイアウトを見ると、変数a、変数cと変数dのサイズの和は4 bytesです。一緒に並びましょう。
struct MyStruct
{
public byte a; //1byte
public byte d; //1byte
public short c; //2byte
public int b; //4byte
}
今回SizeOfで確認すると8 bytesを返します。レイアウトをコンパクトしました。無駄にさようなら!
[byte][byte][short][short]
[int][int][int][int]
そして2つのレイアウトは効率にも差があります。1000000の構造体を生成するテストプログラムを作ります。
public struct MyStruct
{
public byte a; //1byte
public byte d; //1byte
public short c; //2byte
public int b; //8byte
public void modify(byte a)
{
this.a = a;
}
}
public class Struct2
{
public byte a; //1byte
public int b; // 4byte
public short c; //2byte
public byte d; //1byte
}
private const int Num = 1000000;
public static void Main(string[] args)
{
Stopwatch sw = new Stopwatch();
sw.Start();
List<MyStruct> list = new List<MyStruct>();
for (int i = 0; i < Num; i++)
{
list.Add(new MyStruct());
}
sw.Stop();
Console.WriteLine("Struct1 Elapsed: " + sw.Elapsed);
GC.Collect();
Thread.Sleep(1000);
sw.Restart();
var list2 = new List<Struct2>();
for (int i = 0; i < Num; i++)
{
list2.Add(new Struct2());
}
sw.Stop();
Console.WriteLine("Struct2 Elapsed: " + sw.Elapsed);
}
リザルト
Struct1 Elapsed: 00:00:00.0208816
Struct2 Elapsed: 00:00:00.0616822
構造体だけではなく、クラスにも同じ原理です。日常のプログラミングには特にメモリレイアウトを書きながら変数を移動しなくてもいいです。ただ、同じタイプの変数を一緒に並ぶと無駄を結構減っています。