15

I am working on optimization of memory consuming application. In relation to that I have question regarding C# reference type size overhead.

The C# object consumes as many bytes as its fields, plus some additional administrative overhead. I presume that administrative overhead can be different for different .NET versions and implementations.

Do you know what is the size (or maximum size if the overhead is variable) of the administrative overhead for C# objects (C# 4.0 and Windows 7 and 8 environment)?

Does the administrative overhead differs between 32- or 64-bit .NET runtime?

Cœur
  • 37,241
  • 25
  • 195
  • 267
Tom
  • 26,212
  • 21
  • 100
  • 111
  • 2
    If you knew the answer to this question, what could you do with it? – Jon Jan 11 '13 at 20:54
  • 4
    Why not just use a memory profiler? – John Saunders Jan 11 '13 at 20:59
  • "Adminstrative overhead" = "object class descriptor". Generally (as @Reed Copsey says below), it's about two words, one for the class type/pointer and another for thread/semaphore locking. – David R Tribble Jan 11 '13 at 21:02
  • @Jon, if the overhead takes relatively significant amount of memory, I would "join" some classes. The application I mentioned has lots of small (a few properties) classes which do not make much sense from architecture point of view. So before any changes, I want to discover all pros and cons. – Tom Jan 11 '13 at 21:05
  • 5
    Do not break the architecture of your app by joining small classes together unless they actually do make sense to be in one class. *Small classes are good.* Follow @JohnSaunders advice. If you have a memory problem, use a memory profiler. Don't guess. Know. – Anthony Pegram Jan 11 '13 at 21:13
  • 1
    To save memory you might have a look at empty forwarding values http://geekswithblogs.net/akraus1/archive/2011/08/18/146583.aspx. This comes in handy if you load may similar objects from disk in a streaming fashion. You can later apply many more tricks like reference sharing and other stuff. – Alois Kraus Jan 11 '13 at 22:19
  • 1
    I am voting to reopen this question because it is not a duplicate. The linked duplicates are about determining the memory size of a class on a specific system at runtime. This question is about the amount of overhead that is included in that size, and how it differs on different systems. – HugoRune Sep 20 '16 at 14:58

3 Answers3

17

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.

Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
  • 1
    the TypeHandle (but not the syncblock, I think) are pointers, so it'd be 8 bytes on x64, yeah? – JerKimball Jan 11 '13 at 21:01
  • There are ways of optimizing 64-bit pointers down to 32 bits; i.e., if all of the class descriptor info blocks fit into the same 4GB segment, then the runtime interpreter only needs to store an offset into the segment instead of a full-width pointer. – David R Tribble Jan 11 '13 at 21:04
  • 1
    @Loadmaster That isn't done, however, at least not in the current runtime. – Reed Copsey Jan 11 '13 at 21:08
  • @Loadmaster Yeah, I know what you're talking about, effectively using short-jumps instead of long-jumps, but I believe Reed's correct - the runtime doesn't take this optimization under any circumstances I'm aware of. – JerKimball Jan 11 '13 at 21:11
  • I can't really tell from that article what the overhead would be, but what I have found elsewhere says that the answer is wrong: http://www.simple-talk.com/dotnet/.net-framework/object-overhead-the-hidden-.net-memory--allocation-cost/ http://stackoverflow.com/questions/10655829/what-is-the-memory-overhead-of-a-net-object – Guffa Jan 12 '13 at 18:17
  • @Guffa As of framework 3.5sp1, that article did seem to fit my real-world experimentations and measurements. It's difficult to tell with a single object allocation, however, as alignment issues can make things seem bigger many times. With profiling of large-scale data allocations seem to suggest it's correct, at least in the root portion. I haven't checked again since CLR 4, however. – Reed Copsey Jan 12 '13 at 22:25
  • @ReedCopsey: What was that a reply to? It didn't seem like a reply to my last comment. – Guffa Jan 13 '13 at 01:02
  • @ReedCopsey the link Drill Into .NET Framework Internals to See How the CLR Creates Runtime Objects does not exist anymore, do you have any new info or update ^^ +1 * 2 – Bassam Alugili Aug 10 '16 at 08:58
  • can you please help me with some documentation/blog/link which helps me understand this - `Also, there may be padding for types to fit on address boundaries, though this depends a lot on the type in question`? – RBT Aug 16 '16 at 09:02
  • I tested. An empty object takes 24 Bytes in my test, but the first 8 Bytes of data in the object is included. I.e. Empty object size: 24B. Object with 2 integer properties: 24B. Object with 3 integer properties: 28B – JohnF May 04 '20 at 16:25
9

There is an article online with the title "The Truth About .NET Objects And Sharing Them Between AppDomains" which shows some rotor source code and some results of experimenting with objects and sharing them between app domains via a plain pointer.

http://geekswithblogs.net/akraus1/archive/2012/07/25/150301.aspx

  • 12 bytes for all 32-bit versions of the CLR
  • 24 bytes for all 64-bit versions of the CLR

You can do test this quite easily by adding millions of objects (N) to an array. Since the pointer size is known you can calculate the object size by dividing the value by N.

var initial = GC.GetTotalMemory(true);
const int N = 10 * 1000 * 1000;
var arr = new object[N];
for (int i = 0; i < N; i++)
{
    arr[i] = new object();
}

var ObjSize = (GC.GetTotalMemory(false) - initial - N * IntPtr.Size) / N;

to get an approximate value on your .NET platform.

The object size is actually defined to allow the GC to make assumptions about the minimum object size.

\sscli20\clr\src\vm\object.h

//
// The generational GC requires that every object be at least 12 bytes
// in size.   
#define MIN_OBJECT_SIZE     (2*sizeof(BYTE*) + sizeof(ObjHeader))

For e.g. 32 bit this means that the minimum object size is 12 bytes which do leave a 4-byte hole. This hole is empty for an empty object but if you add e.g. int to your empty class then it is filled and the object size stays at 12 bytes.

Jason Sparc
  • 702
  • 2
  • 7
  • 29
Alois Kraus
  • 13,229
  • 1
  • 38
  • 64
4

There are two types of overhead for an object:

  • Internal data used to handle the object.
  • Padding between data members.

The internal data is two pointers, so in a 32-bit application that is 8 bytes, and in a 64-bit application that is 16 bytes.

Data members are padded so that they start on an even address boundary. If you for example have a byte and an int in the class, the byte is probably padded with three unused bytes so that the int starts on the next machine word boundary.

The layout of the classes is determined by the JIT compiler depending on the architecture of the system (and might vary between framework versions), so it's not known to the C# compiler.

Guffa
  • 687,336
  • 108
  • 737
  • 1,005
  • 1
    @ReedCopsey: I can't really tell from that article, but this article for example states the opposite: http://www.simple-talk.com/dotnet/.net-framework/object-overhead-the-hidden-.net-memory--allocation-cost/ – Guffa Jan 11 '13 at 21:15
  • @usr: What are you referring to? – Guffa Jan 13 '13 at 01:03
  • On 64 bits the overhead is 12 bytes, not 16. – usr Jan 13 '13 at 11:21
  • @usr: Do you have anything to back that up? It's not so from what I have found: http://www.simple-talk.com/dotnet/.net-framework/object-overhead-the-hidden-.net-memory--allocation-cost/ http://stackoverflow.com/questions/10655829/what-is-the-memory-overhead-of-a-net-object – Guffa Jan 13 '13 at 11:24
  • It looks like the minimum object size is 16 but you can store 4 bytes "for free" in it. So new object() is 16 bytes but new { X = 0 } is also 16 bytes. I did not know that.; The overhead for useful objects is 12 bytes, though. – usr Jan 13 '13 at 11:43
  • @usr: I think that you are confusing 32 bit and 64 bit environments. The 32 bit memory manager allocates memory in blocks of 16 bytes, but the 64 bit memory manages doesn't. – Guffa Jan 13 '13 at 12:22
  • 1
    http://msmvps.com/blogs/jon_skeet/archive/2011/04/05/of-memory-and-strings.aspx prooves you're right. The x64 overhead is 16 bytes. Where did I get the 12 bytes number from? I remember reading that multiple times on the web. – usr Jan 13 '13 at 13:18