TheDeveloperBlog.com

Home | Contact Us

CSharp | Java | Python | Swift | GO | WPF | Ruby | Scala | F# | JavaScript

C# Class Fields: Ordering, Memory

This C# article tests the ordering of fields in classes. It measures memory usage. It describes how fields are packed.

Class fields. In a class, does the ordering of fields matter?

In older languages, sometimes it does—bytes must be next to each other to fit into 4-byte words. But in the C# language, ordering makes no difference.

Byte

Here: I test how effectively the C# compiler "packs" fields. I measure memory usage of classes. I break down all the allocations.

Based on:

.NET 4.5, 32 bits

Example. We begin with this program. In class "A", the byte and int fields are not grouped by their type. But in class "B", the bytes and ints are grouped by type. The Main method allocates 100,000 instances of both classes.

And: We use GC.GetTotalMemory to collect garbage and report the memory usage for the arrays.

Info: The array memory usage is measured. It has 100,000 elements. The object memory usage is also measured. There are 100,001 objects.

C# program that tests class field ordering

using System;

class A
{
    byte b1;
    int i1;
    byte b2;
    int i2;
    byte b3;
    int i3;
    byte b4;
    int i4;
}

class B
{
    byte b1;
    byte b2;
    byte b3;
    byte b4;
    int i1;
    int i2;
    int i3;
    int i4;
}

class Program
{
    static void Main()
    {
	// ... Measure class "A".
	long m1 = GC.GetTotalMemory(true);
	A[] ar1 = new A[100000];
	for (int i = 0; i < ar1.Length; i++)
	{
	    ar1[i] = new A();
	}
	long m2 = GC.GetTotalMemory(true);
	Console.WriteLine(m2 - m1);
	Console.WriteLine((m2 - m1) / 100000);
	ar1[0] = null;
	ar1 = null;

	// ... Measure class "B".
	long m3 = GC.GetTotalMemory(true);
	B[] ar2 = new B[100000];
	for (int i = 0; i < ar2.Length; i++)
	{
	    ar2[i] = new B();
	}
	long m4 = GC.GetTotalMemory(true);
	Console.WriteLine(m4 - m3);
	Console.WriteLine((m4 - m3) / 100000);
	ar2[0] = null;
	ar2 = null;
    }
}

Output

3200016  [bytes total, A]
32       [bytes per element, A]
3200016  [bytes total, B]
32       [bytes per element, B]

Next, I sought to determine how much memory is used for various parts of the program. The class reference (within the arrays) accounted for 4 bytes per object. An empty class (with no fields) required 12 bytes.

However: A class with just one int field also required 12 bytes. This is the smallest class size possible.

I included the 4 bytes from the first int into the minimum class size. Then I added all the remaining fields. The four byte fields occupied only four bytes. They were effectively packed together in memory.

Memory usage:

Class reference:  4 bytes
Class, empty:     12 bytes
Class with 1 int: 12 bytes

Int: 4 bytes
     4 bytes
     4 bytes
     4 bytes

Byte: 1 byte
      1 byte
      1 byte
      1 byte

Total: 4 bytes (reference)
       12 bytes (class with 1 int)
       12 bytes (3 remaining ints)
       4 bytes (fields)
       = 32 bytes

Discussion. The ordering of fields in a class has no effect on memory usage. The C# compiler uses a virtualized layout system. It reorders the fields in your types to fit into the smallest amount of memory.

So: The C# compiler is pretty smart. We don't need to reorder our fields. It can do this on its own.

Also, I found that a class with one field is the same size as an empty class. This means there is no advantage to using empty classes. I recommend just using the clearest code in most situations.

Summary. Reordering fields in classes serves no technical purpose. It may make classes easier to understand. I already knew this was true in this language, but I investigated further. I tested on a 32-bit platform.