Size of a managed object

We don't expose the managed size of objects because we want to reserve the ability to change the way we lay these things out. For example, on some systems we might align and pack differently. For this to happen, you need to specify
tdAutoLayout for the layout mask of your ValueType or Class. If you specify tdExplicitLayout or tdSequentialLayout, the CLR’s freedom to optimize your layout is constrained.

If you are curious to know how big an object happens to be, there are a variety of ways to discover this. You can look in the debugger. For example, Strike or SOS (son-of-strike) shows you how objects are laid out. Or you could allocate two objects and then use unverifiable operations to subtract the addresses. 99.9% of the time, the two objects will be adjacent. You can also use a managed profiler to get a sense of how much memory is consumed by instances of a particular type.

But we don't want to provide an API, because then you could form a dependency over this implementation detail.

Some people have confused the System.Runtime.InteropServices.Marshal.SizeOf() service with this API. However, Marshal.SizeOf reveals the size of an object after it has been marshaled. In other words, it yields the size of the
object when converted to an unmanaged representation. These sizes will certainly differ if the CLR’s loader has re-ordered small fields so they can be packed together on a tdAutoLayout type.

Comments (11)
  1. Chris Brumme says:

    Sam’s post reminded me of an unpleasant bug that is related to this. If you run the following program, you will see that the CLR marshals the struct to the correct unmanaged layout.

    But examine the bits that are dumped for the struct in its managed form. The first character has been packed with the 2nd character and the layout is not sequential.

    If you only care about the layout after marshaling has occurred, this won’t affect you. But if you care about direct access to managed data via C#’s ‘unsafe’ feature of Managed C++’s ability to perform unsafe casting, this is a real problem.

    You can avoid this problem by using ExplicitLayout rather than SequentialLayout. However, this is unattractive for a number of reasons. First, C# defaults to Sequential. Second, it’s much clumsier to specify. Third, your code must have elevated security. Fourth, it is difficult to describe structures in a manner that works correctly and automatically with both 32 and 64 bit systems. (SequentialLayout of IntPtr is the best way to achieve this).

    Another option is to manually pack chars c1 & c2 together in the managed layout. Most developers do this even without thinking because it’s the efficient thing to do. This is probably the reason that the bug wasn’t noticed earlier.

    Until this bug is addressed, I would recommend that you manually pack small scalars on your Sequential classes and structs, so that the CLR’s layout engine doesn’t cause problems here.

    using System;
    using System.Runtime.InteropServices;

    struct B
    public char c1;
    public int i1;
    public char c2;

    class c
    public static void Main(string[] args)
    IntPtr ofs1;

        B b = new B();
        b.c1 = 'a';
        b.i1 = 1;
        b.c2 = 'b';
        ofs1 = Marshal.OffsetOf(typeof(B), "i1");
        Console.WriteLine("B.i1 at " + ofs1);
        ofs1 = Marshal.OffsetOf(typeof(B), "c2");
        Console.WriteLine("B.c2 at " + ofs1);
            char *p = &b.c1;
                byte *p2 = (byte *) p - 4;
                for (int i=0; i<16; i++, p2++)
                    Console.Write(*p2 + " ");


  2. Rocky Moore says:

    -We don’t expose the managed size of objects because we want to reserve the ability to change the way we lay these things out.-

    That is exactly why it would be great to have a "SizeOf" memer for objects. Not for the purpose of determining that the object is in memory at a given location and continues on for (x) number of bytes but that the object consumes (x) about of bytes.

    My goal is not for moving, copying or accessing an object but just for memory consumption calculations. This allows a program to dynamically determine how much memory individual objects consume.

    Recently, I needed this functionality when I built my DAL layer. I wanted to cache (x) amount of Rows, Tables, Datasets locally based on their consumption against the available virtual memory or private configuration limits. Since it was impossible to know how much space each object consumed dynamically, I had to guess and hard code that guess into the program.

    A dynamic SizeOf would have given the information required and made this calculation dynamic as the client is running.

    I guess the point is, it would be useful to have a SizeOf functionality for the purposes of memory consumption along with a possible parameter to specify if it should include memory consumed by ref values also.

  3. Chris Brumme says:

    Rocky, what we’ve found with caching is that using a retained size is a poor heuristic. A better heuristic is to select a desired hit rate and then tune for that. Without considering hit rate, we find that some caches are too small to do any real good and others (all too often) cache the world.

    A really large cache might have a hit rate that is only incrementally better than a much smaller one. And when you factor in the secondary impact to the process from having all that extra memory tied up (like more expensive Generation 2 collections, possibly more write barrier activity leading to Generation 0 costs, paging on the client, CPU cache pressure, etc.) the larger cache is often much worse than a smaller one.

  4. Sunil Menon says:

    Dear All,
    I guess I am trying to dig a well in a desert to find water….just one more query…I tried the following…
    <System.Runtime.InteropServices.DllImport("kernel32.dll")> _
    Public Shared Function GlobalSize(ByVal hMem As Long) As Long
    End Function
    Dim FrameString As String = "Hello World"
    Dim oHandle As GCHandle = GCHandle.Alloc(FrameString)
    Dim oPtr As IntPtr = New IntPtr(4)
    oPtr = GCHandle.op_Explicit(oHandle)
    Dim oLong As Long = CType(oPtr.ToInt32, Long)
    Dim oSize As Long = GlobalSize(oLong)

    But every time I got the same value in oSize.
    Is this method wrong too…

    Please help…

    Many Regards

  5. Jerome Wilson says:

    I’m in exactly the same situation as Rocky above, albeit a year later 🙂 That is, I’d like to determine the size of objects in order to write caching functionality. Sure, you can constrain by number of items but but it would be much better to be able to constrain by total size of cache. I fully intend to use number of hits to determine what cached obejcts to ‘drop off’ once the cache has reached its maximum storage size. Any chance of the CLR team reconsidering?

  6. Chris Brumme says:

    I think we’ve got past most of our philosophical objections to providing this information, since the ‘sizeof’ IL opcode provides the same information to early-bound callers. But it could still be a long time before you see this feature, because Whidbey is largely baked and Orcas is already looking tight. (In other words, it’s the usual situation for software).

    If you are caching leaf objects, an instance sizeof API would be useful to you. But if you cache instance graphs, you would have to perform some sort of graph walk to determine the true cost of a cache entry — paying attention to instance identity to avoid over-counting or even infinite recursion.

  7. tanliyoung says:

    CLR 中类型字段的运行时内存布局 (Layout) 原理浅析 [1]

  8. tanliyoung says:

    CLR 中类型字段的运行时内存布局 (Layout) 原理浅析 [1]

Comments are closed.

Skip to main content