What Is the Memory Overhead of a .Net Object

What is the memory overhead of a .NET Object

I talk about this in a blog post "Of memory and strings". It's implementation-specific, but for the Microsoft .NET CLR v4, the x86 CLR has a per-object overhead of 8 bytes, and the x64 CLR has a per-object overhead of 16 bytes.

However, there are minimum sizes of 12 and 24 bytes respectively - it's just that you get the first 4 or 8 bytes "free" when you start storing useful information :)

(See the blog post for more information.)

What is the memory overhead of a .NET custom struct type?


Where does that leave custom struct types that you put together in
your application?

They are no different than primitive types. They carry no additional overhead other than the fields they have. The fact that they derive from object doesn't mean they incur the overhead that reference types carry, which is the method table pointer and sync root block.

You can test this out using Marshal.SizeOf:

void Main()
{
var f = new Foo();
Console.WriteLine(Marshal.SizeOf(f));
}

public struct Foo
{
public int X { get; set; }
public int Y { get; set; }
}

This will print 8 in a 32bit mode, which is exactly two integer values (4 bytes each).

Note Marshal.SizeOf will output the size of the unmanaged object. The CLR is still free to re-order fields or pack them together.

C# Object Size Overhead

Typically, there is an 8 or 12 byte overhead per object allocated by the GC. There are 4 bytes for the syncblk and 4 bytes for the type handle on 32bit runtimes, 8 bytes on 64bit runtimes. For details, see the "ObjectInstance" section of Drill Into .NET Framework Internals to See How the CLR Creates Runtime Objects on MSDN Magazine.

Note that the actual reference does change on 32bit or 64bit .NET runtimes as well.

Also, there may be padding for types to fit on address boundaries, though this depends a lot on the type in question. This can cause "empty space" between objects as well, but is up to the runtime (mostly, though you can affect it with StructLayoutAttribute) to determine when and how data is aligned.

How much memory does a C#/.NET object use?

You could use a memory profiler like

.NET Memory Profiler (http://memprofiler.com/)

or

CLR Profiler (free) (http://clrprofiler.codeplex.com/)

C# Where does the memory overhead come from

Memory overhead

Your cVector class adds alot of memory overhead. On a 32-bit system, any reference object has a memory overhead of 12 bytes (although 4 of those are free to be used by fields if possible), if I recall correctly. Let's go with an overhead of 8 bytes. So in your case with 10,000,000 triangles, each containing 4 vectors, that adds upp to:

10,000,000 * 4 * 8 = 305 MB of overhead

If you're running on a 64-bit system it's twice that:

10,000,000 * 4 * 16 = 610 MB of overhead

On top of this, you also have the overhead of the four references each cSTLTriangle will have to the vectors, giving you:

10,000,000 * 4 * 4 = 152 MB (32-bit)

10,000,000 * 4 * 8 = 305 MB (64-bit)

As you can see this all builds up to quite a hefty bit of overhead.

So, in this case, I would suggest you make cVector a struct. As discussed in the comments, a struct can implement interfaces (as well as properties and methods). Just be aware of the caveats that @Jcl mentioned.

You have the same issue with your cSTLTriangle class (about 76/152 MB overhead for 32-bit and 64-bit, respectively), although at its size I'm not sure I want to recommend going with struct on that. Others here might have useful insights on that matter.

Additionally, due to padding and object layout, the overhead might actually be even larger, but I haven't taken that into account here.

List capacity

Using the List<T> class with that amount of objects can cause some wasted memory. As @Matthew Watson mentions, when the list's internal array has no more room, it will be expanded. In fact, it will double it's capacity every time that happens. In a test with your number of 10533050 entries, the capacity of the list ended up at 16777216 entries, giving an overhead of:

( 16777216 - 10533050 ) * 4 byte reference = 23 MB (32-bit)

( 16777216 - 10533050 ) * 8 byte reference = 47 MB (64-bit)

So since you know the number of triangles in advance, I would recommend just going with a simple array. Manually setting the Capacity of a list works too.

Other issues

The other issues that have been discussed in the comments should not give you any memory overhead, but they sure will put alot of unnecessary pressure on the GC. Especially the SubArray method which, while very practical, will create many millions of garbage arrays for the GC to handle. I suggest skipping that and indexing into the array manually, even if it's more work.

Another issue is reading the entire file at once. This will be both slower and use more memory than reading it piece by piece. Directly using a BinaryReader as others have suggested might not be possible due to the endianness issues you need to deal with. One complicated option could be to use memory mapped files, that would let you access the data without having to care about if it's been read or not, leaving the details to the OS.

(man I hope I got all these numbers right)

List object memory overhead

This is a common problem of allocating large amounts of tiny objects: object overhead, which you can usually disregard, comes into play.

37*10^6 * 4 bytes = 148 Mb

Assuming that an array of four bytes would occupy four bytes in memory is incorrect. In addition to the payload of four bytes, array object must store array's length, a sync block, and a type pointer. This works out to 12 bytes of overhead on a 32-bit system, or 24 bytes on a 64-bit system.

In addition to individual overheads of array objects you need to factor in the overhead of memory allocator, overhead of memory alignment, and overhead of garbage collector. All things taken together, it is not unreasonable to see the total memory in use grow to 2 Gb.

One way to fix this is to switch to a list of uints, which do occupy four bytes each. When you need to store four bytes, convert them to uint, and store it in the list. When you need your bytes back, convert uint into a temporary four-byte array. Use BitConverter to deal with converting between uints and arrays of bytes.

What does the .NET CLR do with the memory overhead in empty types?

Ah, I see what is going on.

Arrays store their length as the first 4 or 8 bytes (in 32 and 64-bit systems, respectively) of their instance field data (after the type pointer). In order to get the size of memory usage, the CLR gets the base size from the method table (pointed to by the type pointer) and adds the length multiplied by the per-item size (which it also gets from the method table).

In other words, the formula is:

memory size = base size + length * item size

Rather than have one formula for arrays and a different one for other types, the implementors of the CLR wanted to have a single formula for both, thus eliminating the need for any conditional logic when getting the memory size.

But how could that work? Other object types don't store a length in the first 4 or 8 bytes of their instance field data.

The key is the item size, which is stored in the method table. For non-array types, the item size is 0. This means that length * item size will always be 0 regardless of the value stored in the first 4 or 8 bytes of the instance field data, and the formula will work.

But even though the value of the first 4 or 8 bytes of instance field data doesn't matter, it still needs to be allocated to prevent access violations.

Thanks to Hans for pointing me to the SetFree method. Once I saw that the CLR was essentially converting an arbitrary object to a 1-dimensional byte array, I realized that it was assuming that everything could be converted in that manner, and the investigation into why led me to this answer.

Overhead of a .NET array?

Here's a slightly neater (IMO) short but complete program to demonstrate the same thing:

using System;

class Test
{
const int Size = 100000;

static void Main()
{
object[] array = new object[Size];
long initialMemory = GC.GetTotalMemory(true);
for (int i = 0; i < Size; i++)
{
array[i] = new string[0];
}
long finalMemory = GC.GetTotalMemory(true);
GC.KeepAlive(array);

long total = finalMemory - initialMemory;

Console.WriteLine("Size of each element: {0:0.000} bytes",
((double)total) / Size);
}
}

But I get the same results - the overhead for any reference type array is 16 bytes, whereas the overhead for any value type array is 12 bytes. I'm still trying to work out why that is, with the help of the CLI spec. Don't forget that reference type arrays are covariant, which may be relevant...

EDIT: With the help of cordbg, I can confirm Brian's answer - the type pointer of a reference-type array is the same regardless of the actual element type. Presumably there's some funkiness in object.GetType() (which is non-virtual, remember) to account for this.

So, with code of:

object[] x = new object[1];
string[] y = new string[1];
int[] z = new int[1];
z[0] = 0x12345678;
lock(z) {}

We end up with something like the following:

Variables:
x=(0x1f228c8) <System.Object[]>
y=(0x1f228dc) <System.String[]>
z=(0x1f228f0) <System.Int32[]>

Memory:
0x1f228c4: 00000000 003284dc 00000001 00326d54 00000000 // Data for x
0x1f228d8: 00000000 003284dc 00000001 00329134 00000000 // Data for y
0x1f228ec: 00000000 00d443fc 00000001 12345678 // Data for z

Note that I've dumped the memory 1 word before the value of the variable itself.

For x and y, the values are:

  • The sync block, used for locking the hash code (or a thin lock - see Brian's comment)
  • Type pointer
  • Size of array
  • Element type pointer
  • Null reference (first element)

For z, the values are:

  • Sync block
  • Type pointer
  • Size of array
  • 0x12345678 (first element)

Different value type arrays (byte[], int[] etc) end up with different type pointers, whereas all reference type arrays use the same type pointer, but have a different element type pointer. The element type pointer is the same value as you'd find as the type pointer for an object of that type. So if we looked at a string object's memory in the above run, it would have a type pointer of 0x00329134.

The word before the type pointer certainly has something to do with either the monitor or the hash code: calling GetHashCode() populates that bit of memory, and I believe the default object.GetHashCode() obtains a sync block to ensure hash code uniqueness for the lifetime of the object. However, just doing lock(x){} didn't do anything, which surprised me...

All of this is only valid for "vector" types, by the way - in the CLR, a "vector" type is a single-dimensional array with a lower-bound of 0. Other arrays will have a different layout - for one thing, they'd need the lower bound stored...

So far this has been experimentation, but here's the guesswork - the reason for the system being implemented the way it has. From here on, I really am just guessing.

  • All object[] arrays can share the same JIT code. They're going to behave the same way in terms of memory allocation, array access, Length property and (importantly) the layout of references for the GC. Compare that with value type arrays, where different value types may have different GC "footprints" (e.g. one might have a byte and then a reference, others will have no references at all, etc).
  • Every time you assign a value within an object[] the runtime needs to check that it's valid. It needs to check that the type of the object whose reference you're using for the new element value is compatible with the element type of the array. For instance:

    object[] x = new object[1];
    object[] y = new string[1];
    x[0] = new object(); // Valid
    y[0] = new object(); // Invalid - will throw an exception

This is the covariance I mentioned earlier. Now given that this is going to happen for every single assignment, it makes sense to reduce the number of indirections. In particular, I suspect you don't really want to blow the cache by having to go to the type object for each assigment to get the element type. I suspect (and my x86 assembly isn't good enough to verify this) that the test is something like:

  • Is the value to be copied a null reference? If so, that's fine. (Done.)
  • Fetch the type pointer of the object the reference points at.
  • Is that type pointer the same as the element type pointer (simple binary equality check)? If so, that's fine. (Done.)
  • Is that type pointer assignment-compatible with the element type pointer? (Much more complicated check, with inheritance and interfaces involved.) If so, that's fine - otherwise, throw an exception.

If we can terminate the search in the first three steps, there's not a lot of indirection - which is good for something that's going to happen as often as array assignments. None of this needs to happen for value type assignments, because that's statically verifiable.

So, that's why I believe reference type arrays are slightly bigger than value type arrays.

Great question - really interesting to delve into it :)

What is the memory overhead of storing data in a .NET DataTable?

Well, don't forget that a DataTable stores 2? 3? versions of the data - original and updated (possibly one other?). It also has a lot of references since it is cell-based, and boxing for any value-types. It would be hard to quantify the exact memory...

Personally, I very rarely use DataTable - typed POCO classes are a much more sensible bet in my view. I wouldn't use an array (directly), though - List<T> or BindingList<T> or similar would be far more common.

As a crude measure, you could create a lot of tables etc and look at the memory usage; for example, the following shows a ~4.3 factor - i.e. more than 4 times as expensive, but obviously that depends a lot on the number of columns vs rows vs tables etc:

    // takes **roughly** 112Mb  (taskman)
List<DataTable> tables = new List<DataTable>();
for (int j = 0; j < 5000; j++)
{
DataTable table = new DataTable("foo");
for (int i = 0; i < 10; i++)
{
table.Columns.Add("Col " + i, i % 2 == 0 ? typeof(int)
: typeof(string));
}
for (int i = 0; i < 100; i++)
{
table.Rows.Add(i, "a", i, "b", i, "c", i, "d", i, "e");
}
tables.Add(table);
}
Console.WriteLine("done");
Console.ReadLine();

vs

    // takes **roughly** 26Mb (taskman)
List<List<Foo>> lists = new List<List<Foo>>(5000);
for (int j = 0; j < 5000; j++)
{
List<Foo> list = new List<Foo>(100);
for (int i = 0; i < 100; i++)
{
Foo foo = new Foo { Prop1 = "a", Prop3 = "b",
Prop5 = "c", Prop7 = "d", Prop9 = "e"};
foo.Prop0 = foo.Prop2 = foo.Prop4 = foo.Prop6 = foo.Prop8 = i;
list.Add(foo);
}
lists.Add(list);
}
Console.WriteLine("done");
Console.ReadLine();

(based on)

class Foo
{
public int Prop0 { get; set; }
public string Prop1 { get; set; }
public int Prop2 { get; set; }
public string Prop3 { get; set; }
public int Prop4 { get; set; }
public string Prop5 { get; set; }
public int Prop6 { get; set; }
public string Prop7 { get; set; }
public int Prop8 { get; set; }
public string Prop9 { get; set; }
}

Memory usage in .NET when creating a new class or struct

A single reference either takes 4 bytes on 32-bit processes or 8 bytes on 64-bit processes. A reference is a standard overhead on classes (as they are reference types). Structs do not incur references (well, ignoring any potential boxing) and are the size of their content usually. I cannot remember if classes have any more overhead, don't think so.

This question delves into class vs struct (also provided in the question comments):

Does using "new" on a struct allocate it on the heap or stack?

As stated in the comments, only instances of a class will consume this reference overhead and only when there is a reference somewhere. When there are no references, the item becomes eligible for GC - I'm not sure what the size of a class is on the heap without any references, I would presume it is the size of its content.

Really, classes don't have a true "size" that you can rely on. And most importantly this shouldn't really be the deciding factor on using classes or structs (but you tend to find guidelines stating that types at or below roughly 16 bytes can be suitable structs, and above tends towards classes). For me the deciding factor is intended usage.

When talking about structs, I feel obliged to provide the following link: Why are mutable structs “evil”?



Related Topics



Leave a reply



Submit