13

There is a fixed overhead associated with a .NET object as more fully outlined in this SO question: What is the memory overhead of a .NET Object being 12 or 24 bytes depending on whether you are in a 32 bit or 64 bit process.

That said, basic value types like int, double, boolean, etc incur no overhead because they are Value Types.

Where does that leave custom struct types that you put together in your application? On the one hand, they are value types like int, double, boolean above [so should not incur overhead] but on the other hand they derive indirectly from System.Object and so should (technically) incur overhead.

Community
  • 1
  • 1
Sam
  • 2,745
  • 3
  • 20
  • 42
  • 1
    Note that primitive types also derive indirectly from `System.Object` - e.g. `System.Int32 : System.ValueType`. – Jon Skeet Nov 12 '15 at 12:20
  • 1
    Structs don't really derive from object; they successfully pretend that they do through some compiler trickery. All structs are implicitly sealed, so it's not too hard for the compiler (i.e. it needs to track a handful of methods). – Sergey Kalinichenko Nov 12 '15 at 12:26

4 Answers4

9

The size of a struct is determined by the sum of the sizes of its fields plus the padding between the fields that get them aligned properly, plus padding at the end of the struct that ensures that they are still aligned properly when the struct is stored in an array.

So, for one, a struct is not entirely unlikely to contain a field of a reference type. Like a string. In which case the struct is going to be larger since references are pointers under the hood, taking 8 bytes instead of 4.

The padding is the much sneakier detail. In 32-bit mode, variables cannot count on an alignment better than 4. An issue with double and long, 8 byte types that can easily get mis-aligned. Notably affecting perf of a 32-bit program, if a double is misaligned across an L1 cache boundary line then a read or write can be 3x as slow. Also the core reason that these types are not atomic in the C# memory model. Not an issue in 64-bit mode, the CLR then must and does provide an alignment guarantee of 8.

Nevertheless, the CLR does attempt to give such struct members proper alignment in 32-bit mode, even though the struct itself is not guaranteed to be aligned. Otherwise a side-effect of structs having an implicit [StructLayout(LayoutKind.Sequential, Pack=8)] attribute. An oddity in the CLR source code, the C++ statement that does this has no comment. I suspect it was a quicky fix for less than stellar unmanaged interop perf, keeping structs blittable is pretty important to speed.

You'll however not always get this, the CLR gives up if the struct contains a member that is itself a struct that does not have sequential layout. Notably this happens for DateTime and DateTimeOffset, the programmers who wrote them applied the [StructLayout(LayoutKind.Auto)] attribute on them for very mysterious reasons. In the case of DateTimeOffset likely to be a copy/paste bug. Layout of your struct will now be unpredictable, it becomes LayoutKind.Auto as well and the CLR re-arranges fields to minimize the struct size. This can cause extra padding in x64 mode.

These are obscure implementation details that you should never fret about.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
8

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.

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
  • Does `Marshal.SizeOf` return different size, if you declare `Foo` as `class`? – user4003407 Nov 12 '15 at 13:17
  • @PetSerAl It throws an `ArgumentException` since it cant really calculate the size of a class in managed memory. As I said in the post, there are other variants that effect the size of a class other than the fields it has. See [this](http://blogs.msdn.com/b/cbrumme/archive/2003/04/15/51326.aspx) for more. – Yuval Itzchakov Nov 12 '15 at 13:24
  • @MicroVirus What would you add regarding boxing? – Yuval Itzchakov Nov 12 '15 at 13:28
  • Does you mark class with `[StructLayout(StructLayout.Sequential)]`? – user4003407 Nov 12 '15 at 13:30
  • Nothing too much, just it seems that the OP does not fully realise how boxing is the bridge between value types and reference types and how it can add to the memory overhead significantly, for instance when adding value types to a `List` – MicroVirus Nov 12 '15 at 13:30
  • @PetSerAl It will work when you use `StructLayout`, but it won't give you the actual size of the object. – Yuval Itzchakov Nov 12 '15 at 13:32
  • But you use it as proof that `struct` does not have overhead. So if it is not actual size for `class` why it is actual size for `struct`? – user4003407 Nov 12 '15 at 13:35
  • 2
    `Marshal.SizeOf` will give you the size of the struct if it needs marshalling to unmanaged code (i.e. PInvoke), which isn't necessarily the same amount as the CLR will use. – Justin Nov 12 '15 at 13:39
  • @Justin It will give you the size of the unmanaged object. The CLR *may* choose to re-order fields or pack them together, so this isn't 100% accurate to the in-memory object. – Yuval Itzchakov Nov 12 '15 at 13:53
3

You may have some overhead with fields' alignments:

https://msdn.microsoft.com/en-us/library/system.runtime.interopservices.structlayoutattribute(v=vs.100).aspx

E.g.

  public struct Test {
    public Byte A;
    public long B;
  }

will be of size 16 bytes on 64-bit process and 12 bytes on 32-bit process correspondingly (when we could expect 9 bytes only);

Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215
1

Zero.

Unless there's gaps due to alignment, in which case it's the cost of those gaps.

But otherwise the structure is just the same as if the fields were separate variables laid out on the stack.

Box it though, and you're dealing with it via object which has the same overheads as any other reference type.

Jon Hanna
  • 110,372
  • 10
  • 146
  • 251