25

There is a question:

Given:

struct Point {int x; int y;}
var p = new Point[3]

how many bytes of memory will be allocated in stack and in heap if we use a 64-bit processor?

The correct answer for .Net is 44. Can somebody explain how did this number appear?

As far as I understand, p will occupy 8 bytes in stack for x64.

And we have two values of Int32 per structure, thus p.Length * sizeof(Point) 3 * 8 = 24 bytes in heap for an array.

That will be 32 bytes. What is the rest 12 bytes for in this case?

Community
  • 1
  • 1
John Smith
  • 370
  • 4
  • 15

3 Answers3

42

Your answer of 44 bytes is probably a confusion referring to an array of 32 bit architecture.

In .Net (32 bit):

  • Every object contains 4 bytes for synchronization (lock (obj)).
  • Every object contains 4 bytes of its type token.
  • Every array contains 4 bytes of its length.

The pointer is 8 bytes as you said.

This with the 24 bytes of the array itself gives you 44 bytes.


However, this is the header layout for 32 bit.

As you can see, the memory layout of the following code:

var p = new Point[3];
p[0] = new Point { x = 1, y = 2 };
p[1] = new Point { x = 3, y = 4 };
p[2] = new Point { x = 5, y = 6 };

var p2 = new Point[3];
p2[0] = new Point { x = 8, y = 8 };
p2[1] = new Point { x = 8, y = 8 };
p2[2] = new Point { x = 8, y = 8 };

Will be:

Memory layout

You can see the number values in the memory layout as well.


In 64 bit, each field of the header with its takes 8 byte so that the header length is 24 bytes therefore the length of the entire array is 48 bytes and with the variable pointing to the array: 56 bytes.

64 bit architecture memory layout:

64 bit memory layout


Notes:

  • If your array wasn't rounded up to an 8 byte multiple alignment would take place, but it is so alignment is not required. Example (two 1 sized int arrays):

    Alignment

  • Even though the length field of the header is 8 bytes in 64 bit, it's larger than the maximum array size .NET allows therefore only 4 may be used.

Keep in mind that this is an implementation detail and it might change between implementations/versions of the CLR.

Tamir Vered
  • 10,187
  • 5
  • 45
  • 57
  • 1
    so you are limited to 2^32 array members even on 64bit arch? – graywolf Aug 29 '16 at 14:40
  • 4
    @Paladin The indexer is also limited to `int` so its `int.MaxValue` which is `2^31`. – Tamir Vered Aug 29 '16 at 14:45
  • 4
    @TamirVered: Contents of a large file? Scientific/numerical computing where array is obvious type for the situation? ... – R.. GitHub STOP HELPING ICE Aug 29 '16 at 14:54
  • @TamirVered why use signed integer as an index? what was reasoning behind that? (sorry, I know very little about C#, so the answer might be obvious) – graywolf Aug 29 '16 at 14:56
  • 1
    @Paladin probably to avoid the huge number of gotchas associated with math involving signed and unsigned values. (In general .net tries to avoid using unsigned values whenever possible.) IIRC C# requires a lot of extra casts in those cases to (in theory) make the developer acknowledge that something could go wrong. – Dan Is Fiddling By Firelight Aug 29 '16 at 15:06
  • 2
    @R.. Split the file while loading it... To lines/deserialized data/grouped data. Arrange the data in some more complex structure before doing a numerical/scientific computing. I agree that in some cases people would want to do so but there probably will be a better alternative for them. – Tamir Vered Aug 29 '16 at 16:22
  • I don't think this is entirely correct. In 64 bit builds, things get aligned to 8 byte boundaries (4 byte boundaries in 32 bit builds). This will cause the array to consume 48 bytes of memory, with 4 bytes unused in a 64 bit build. – hatchet - done with SOverflow Aug 29 '16 at 18:14
  • @hatchet The image of the memory is from a 64bit build. – Tamir Vered Aug 29 '16 at 18:22
  • 1
    @Paladin Unsigned sizes/indeces are a very common pitfall for bugs. Consider code like `for (size_t i=foo.size(); i >= 0; --i)` which *looks* like it iterates from the last index, but actually is an infinite loop. There are many more cases where it's too easy to write wrong code. – Bakuriu Aug 29 '16 at 18:32
  • @TamirVered - is the right hand side cut off? Because when I count the bytes for your first array in that image, I count 36 bytes, which is what I would expect for a 32 bit build. – hatchet - done with SOverflow Aug 29 '16 at 18:40
  • @hatchet As I said, this is a 64 bit memory image and there is no different between how it would look like in 32 bit. – Tamir Vered Aug 29 '16 at 19:02
  • @Bakuriu Which is a) Why good compilers will complain about a vacuous truth comparison and b) Why you use iterators or enumeration rather than direct index access. – Williham Totland Aug 29 '16 at 19:29
  • @TamirVered something else appears to be going on. I just attempted to replicate your results and got 12 bytes between the two arrays in an x86 build and 24 between them in an x64 one. 32 bit results above 64bit, point data boxed in red, the overhead for the 2nd array between them boxed in green. http://imgur.com/a/NlLwn – Dan Is Fiddling By Firelight Aug 29 '16 at 19:42
  • @DanNeely In your 32 bit results those 12 bytes are exactly those sync/token/length fields of the second array. In your 64 bit results it seems like alignment takes effect, although I guess it's kind of unpredictable and I didn't have this effect both on Any CPU compilation on a 64 bit machine and on a 64 bit compilation. – Tamir Vered Aug 29 '16 at 19:46
  • It is checked, however, your results were affected by the alignment, therefore you have got 48 bytes. – Tamir Vered Aug 29 '16 at 19:53
  • @Tamir Umm yes, objects under x64 have to be correctly aligned which is forced by the CLR. You cannot create unaligned objects safely under x64 with the current CLR. I mean if we start ignoring parts of the object size, why not ignore the object header as well and claim the actual size is 24 or any other number? But if you actually allocated that 3 point struct array anywhere in C# under x64 you'd need 48 byte memory. Just as you'd need 36 in x86. – Voo Aug 29 '16 at 19:54
  • 1
    I mean if you actually would show some screenshot where you manage to to show two point arrays in 72 bytes under x64 (your screenshot is 32bit code where that's clearly no problem) I'll certainly concede the point. Hell I'd even bet a beer that you can't (without unsafe code). On the other hand you might just want to check out line 191 of `object.h` in the coreclr code which shows that the object header has a `PTR_MethodTable` (which as the name says is a pointer) field to see that it's perfectly impossible for your 64bit object header to be as large as the 32bit one.. – Voo Aug 29 '16 at 20:19
  • I need to make a few checks in my computer at work tomorrow where I've got those results and I will edit if anything was wrong, but it is possible that this kind of behaviour changed through the versions of the CLR. I will check with more versions and update tomorrow. – Tamir Vered Aug 29 '16 at 20:32
  • @Tamir Make sure to use actual x64 and not AnyCPU. AnyCPU runs by default as 32bit even on 64bit OSes since .NET 4.5. Alternatively you can remove the "prefer 32bit" flag from the executeable - there's an option in the VS build menu somewhere. – Voo Aug 29 '16 at 20:38
  • If it is somehow version dependent I'm using 2015 pro update 1, running w7pro x64, and was targeting .net 4.5.2. – Dan Is Fiddling By Firelight Aug 29 '16 at 21:02
  • @Voo true and annoying. On the other hand there probably are only 10 people on the entire Stack Overflow who can fact check this. I could but not from memory. – usr Aug 29 '16 at 21:06
  • @Voo Seems like it behaves the same as in your case on my personal computer, I will edit my answer and re-check on my computer at work tomorrow just to make sure about the versions. – Tamir Vered Aug 29 '16 at 21:09
  • 1
    @usr Until a few hours ago I wouldn't've been one - don't think I ever saw the debug-memory dialog used before today. I had a few minutes to kill and decided to find it and produce a tie breaker result. – Dan Is Fiddling By Firelight Aug 29 '16 at 21:16
  • @TamirVered, Could you briefly describe (may be in 3 to 5 lines) what tools/debugger/IDE you used to view into the memory layouts? Tx. – blackpen Aug 30 '16 at 01:44
  • @blackpen In VS (I used 2015 but this feature exists in older versions as well) go to Debug > Windows > Memory 1-4 while debugging. I don't know if any Express/Community version has it too. – Tamir Vered Aug 30 '16 at 04:02
  • @TamirVered, I don't have VS 2015, but I wanted to get a feel of how it works. With your keywords, I found complete details in microsoft's website. Tx. – blackpen Aug 30 '16 at 19:54
  • One correction: The header itself isn't 8 byte aligned, only the total size of the object. An array has 20 byte inherent size. If you had an int array with exactly 1 element the total size would be 24 byte on x64. – Voo Aug 31 '16 at 12:49
  • @Voo Actually, the whole array doesn't require alignment since its size is a multiple of 8 when including the header. – Tamir Vered Aug 31 '16 at 13:52
  • @Tamir The header is 16 byte (before we start arguing on that one just check out the actual source code reference I posted :-) ), array length is 4, structs are 24 meaning 44 byte total. So alignment makes a difference here. – Voo Aug 31 '16 at 14:34
  • @Voo You ignore the sync field, it comes before the header for each `object`. array length is 8, I thought it's 4 too but memory view shows otherwise, look at my memory image for int arrays and struct arrays. – Tamir Vered Aug 31 '16 at 14:45
  • There should only be 2 objects in the header, so that's interesting. It's impossible for the header to be 3 objects because then an empty array would have a size of 32 but it's only 24. It seems there's either some alignment going on or there's an additional DWORD. – Voo Aug 31 '16 at 15:08
  • @Voo Why would it be 32? 3 fields * 8 bytes = 24. – Tamir Vered Aug 31 '16 at 15:12
  • @Tamir Yes so 24 byte for the header and the length field which is 4 byte, which would be 28 byte and get rounded to 32. Since the empty array is only 24 byte, the maximum possible object header size is 20 byte. This will require some additional investigation. – Voo Aug 31 '16 at 15:25
  • @Voo, no wait: 3 fields = sync (8 bytes), type (8 bytes), length (8 bytes), even in 64 bit only 4 bytes of the length field are used but in 64 bit it does take 8. – Tamir Vered Aug 31 '16 at 15:26
13

Most of this is purely an implementation detail and could change with the next version of the CLR.

Run the following program as x86 or x64 and you can empirically determine the size of your structure:

struct Point { int x; int y; }

class Program
{
    const int Size = 100000;

    private static void Main(string[] args)
    {
        object[] array = new object[Size];
        long initialMemory = GC.GetTotalMemory(true);
        for (int i = 0; i < Size; i++)
        {
            array[i] = new Point[3];
        }
        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);

    }
}

The code is pretty simple but was shamelessly stolen from Jon Skeet.

If you do run this you will get the following results:

x86: 36 byte
x64: 48 byte

In the current implementation the size of every object is aligned to the pointer size, this means every object in x86 is 4 byte aligned, and under x64 8 byte (this is absolutely possible to change - HotSpot in Java for example aligns everything to 8 byte even under x86).

Arrays in C# are somewhat special with their length: While they do have a 4 byte length field, under x64 they also include 4 byte additional padding (vm/object.h:766 contains the interesting part). This is most likely done to guarantee that the start of the actual fields is always 8 byte aligned under x64 which is required to get good performance when accessing longs/doubles/pointers (the alternative would be to only add the padding for these types and specialize the length computation - unlikely to be worth the additional complexity).

On x86 the object header is 8 byte and the array overhead 4 byte, which gives us 36 byte.

On x64 the object header is 16 byte and the array overhead 8 byte. This gives us 24 + 24 = 48 byte.

For anyone wanting actual proof instead of empirical tests about the header size and alignment you can just go to the actual source: Here is the object definition of the coreclr. Check out the comment starting at line 178 which states:

// The only fields mandated by all objects are
// 
//     * a pointer to the code:MethodTable at offset 0
//     * a poiner to a code:ObjHeader at a negative offset. This is often zero.  It holds information that
//         any addition information that we might need to attach to arbitrary objects. 

You can also look at the actual code to see that those pointers are actual pointers and not DWORDs or anything else.

The code for aligning the object sizes is also in the same file:

#define PTRALIGNCONST (DATA_ALIGNMENT-1)

#ifndef PtrAlign
#define PtrAlign(size) \
    ((size + PTRALIGNCONST) & (~PTRALIGNCONST))
#endif //!PtrAlign

DATA_ALIGNMENT is defined as 4 for x86 (vm/i386/cgencpu.h) and ARM (vm/arm/cgencpu.h) and 8 for x64 (vm/amd64/cgencpu.h). The code itself is nothing but a standard optimized "round to next multiple of DATA_ALIGNMENT" assuming data alignment is a power of 2 method.

Tamir Vered
  • 10,187
  • 5
  • 45
  • 57
Voo
  • 29,040
  • 11
  • 82
  • 156
  • @hatchet I also added some references to the coreclr source code that support the claim as well. Never looked at that before but I'm surprised (probably shouldn't be there's only so much sensible hierarchies for a VM I guess) how similar the structure is to HotSpot - easy to find your way around. – Voo Aug 29 '16 at 20:33
  • 1
    As a result of its being an implementation detail it's useful to note this answer is completely inapplicable to Mono, where I get 56 bytes on x64. – cat Aug 30 '16 at 00:11
  • @cat Including the variable pointing to the array?, That's what you should get in .NET as well... Read my answer. – Tamir Vered Aug 30 '16 at 04:04
  • 2
    Good answer, but you should link to the Jon Skeet post from which you stole the code sample, shamelessly or otherwise. – Cody Gray - on strike Aug 30 '16 at 07:29
  • @Cody It's an old test project I have lying around, didn't copy it from SO directly when writing this post, so a bit hard to find. If I find time I'll go looking for the reference. – Voo Aug 30 '16 at 09:08
  • @cat Yup no surprise there. I guess header size in mono is 20 or 24 byte under mono for x64. If you'd write an answer for that it'd be rather interesting. – Voo Aug 30 '16 at 17:53
  • 2
    @Voo Skeet's source code is here https://codeblog.jonskeet.uk/2011/04/05/of-memory-and-strings/ – hatchet - done with SOverflow Aug 30 '16 at 18:12
  • @hatchet Mille gracie! Added link – Voo Aug 30 '16 at 18:13
  • @Voo One small correction, the length field is ***8 bytes*** in ***64 bit*** but only ***4 bytes*** because the maximum size of an array in `.NET` is smaller, therefore alignment doesn't take place because the size is a multiple of 8. – Tamir Vered Aug 31 '16 at 13:54
  • @Voo - Although it's a little dated, and seems to speak mostly in terms of 32 bit builds, there's a lot of information in the first article in MSDN May 2005. You can download it at the bottom of this web page (it's no longer online) https://msdn.microsoft.com/magazine/msdn-magazine-issues – hatchet - done with SOverflow Aug 31 '16 at 18:44
  • Also http://www.codeproject.com/Articles/20481/NET-Type-Internals-From-a-Microsoft-CLR-Perspecti – hatchet - done with SOverflow Aug 31 '16 at 18:53
  • @hatchet Found it myself: `vm/object.h:765` specifies a 4 byte padding value for arrays. In retrospective rather obvious why they're doing this: In x86 the start of the actual fields is 8 byte aligned, in x64 it would otherwise only be 4 byte aligned. Since the array class is shared for all one dimensional arrays this would be problematic since reference types and long/doubles really ought to be 8 byte aligned. So I guess instead of handling those cases separately they just make sure everything is properly aligned. – Voo Aug 31 '16 at 19:09
  • `If` everyone `could` just stop `highlighting` single `numbers` as code `for` no purpose `whatsoever` I'd be `quite` happy. This `is` not the `point` of the `code` tag and does not `in` any way shape `or` form improve the `readability`. It's sad that such edits are actually accepted by the majority these days (thanks to the two sole hideouts who actually do a good job and disapproved) – Voo Sep 12 '16 at 16:55
1

Speaking about x86 architecture, the answer of 44 bytes is not correct, because the object reference size in x86 is 4 bytes, not 8 bytes, thus the object length of 36 bytes + 4 bytes of reference to the object gives 40 bytes. Correct me if I'm wrong.

Tamir Vered
  • 10,187
  • 5
  • 45
  • 57