none
Why does struct need to be not more than 16 bytes ? RRS feed

  • Question

  • Hi,

    I've seen in couple of books, blogs that when you define a type as a struct, be sure it does not exceed 16 bytes per instance. Could somebody explain me why is that? I'm curious and I would be grateful for any thorough explanation.

    I assume that this must be related to how a processor read values from stack. Does it perform 16-byte read/write operations as atomic operations ?

    Thank you
    Wednesday, May 21, 2008 6:43 PM

Answers

  • Value types are allocated on the stack and passed around by value and not by reference.  The 16-byte line (BTW, the quotes I recall from books are 64-bytes, but there aren't two quotes that are the same) is a reasonable line to draw between a reasonable value type, where the allocation savings are more significant than the pass-by-value semantics overhead.

     

    Generally speaking, you should measure.  It's entirely possible that larger types will benefit from being a value type; and it's also likely that you'll encounter scenarios where reference semantics are much more convenient and so use a reference type even when the type is very small.

    Wednesday, May 21, 2008 7:34 PM
  • Admittedly, this is the first I've heard of this.  It might have something to do with the size of a cache-line, the unit of data transfer between the CPU cache and RAM.  Cache-lines are between 16 and 256 bytes, 64 bytes is a popular choice.

    Making a large structure in a .NET language is pretty hard.  The usual unmanaged way of blowing them up was by embedding strings or arrays.  That doesn't have much of an effect in .NET, those are reference types and don't take more than 4 bytes.  Let's say a structure is large if it has 100 members, 400 bytes give or take.  A structure instance can be stored either in the stack or on the heap (as a field member of a class or array element).  No need to worry about the heap, gobs of megabytes.  The stack isn't going to cramped either by a 400 byte structure, it's got about a megabyte to give.  Storage size is not the issue.

    Copying structures is another matter.  It happens implicitly when you pass a structure instance as an argument to a method.  This is where the size of the cache-line gets relevant.  The code that the JIT compiler generates is not pretty btw, lots of MOV instructions.  There is a very simple workaround for this, pass the structure by reference.  ByRef in VB.NET, "ref" in C#.  The compiler now simply passes a pointer rather than copying every byte in the structure.

    My advice would be: start passing structures by reference once they start to get large.  Don't let it cramp your design otherwise.  If you think you have a problem, measure first.

    Wednesday, May 21, 2008 8:44 PM
    Moderator
  • It should also be noted that structs exist on the stack and there is a limited amount of stack space.  I beleive the default is 1 megabytes for a .net application.  The max stack size is stored in the pe header.  This can be changed using the editbin program that cames with visual c++.  Also like nobugs said when dealing with large struct you should pass them around by ref so you are just copying a 4 or 8 byte pointer to the struct and not the entire thing.  Here is a quick example to show why creating very large structs can be bad.  Uncommenting either of the lines below will cause stack overflow.

    Code Snippet

            static void Main(string[] args)
            {
                LargeStruct t = new LargeStruct();
             //  LargeStruct t2 = new LargeStruct();

                NoOverflow(ref t);
               // CauseStackOverflow(t);
            }

            private static void NoOverflow(ref LargeStruct t)
            {
            }
            private static void CauseStackOverflow(LargeStruct t)
            {
            }

            [StructLayout(LayoutKind.Sequential, Size = 524288)]
            public struct LargeStruct
            {
            }

    Wednesday, May 21, 2008 10:12 PM
  • It's just the author's proposal for a structure that can still be considered small.  Four members.  Good choice.  Note the phrase "approximately".
    Thursday, May 22, 2008 2:53 PM
    Moderator
  • By the way, these recommendations would also depend on bitness (because on 64-bit you have instructions for copying more data around in a single instruction).  Additionally, in the SP1 JIT (on x86; x64 didn't have the limitation) methods with complex value type parameters can get inlined, thus reducing the overhead of parameter passing in many cases.

     

    So as nobugz said - this is just a number.

    Friday, May 23, 2008 6:06 AM

All replies

  • Value types are allocated on the stack and passed around by value and not by reference.  The 16-byte line (BTW, the quotes I recall from books are 64-bytes, but there aren't two quotes that are the same) is a reasonable line to draw between a reasonable value type, where the allocation savings are more significant than the pass-by-value semantics overhead.

     

    Generally speaking, you should measure.  It's entirely possible that larger types will benefit from being a value type; and it's also likely that you'll encounter scenarios where reference semantics are much more convenient and so use a reference type even when the type is very small.

    Wednesday, May 21, 2008 7:34 PM
  • Admittedly, this is the first I've heard of this.  It might have something to do with the size of a cache-line, the unit of data transfer between the CPU cache and RAM.  Cache-lines are between 16 and 256 bytes, 64 bytes is a popular choice.

    Making a large structure in a .NET language is pretty hard.  The usual unmanaged way of blowing them up was by embedding strings or arrays.  That doesn't have much of an effect in .NET, those are reference types and don't take more than 4 bytes.  Let's say a structure is large if it has 100 members, 400 bytes give or take.  A structure instance can be stored either in the stack or on the heap (as a field member of a class or array element).  No need to worry about the heap, gobs of megabytes.  The stack isn't going to cramped either by a 400 byte structure, it's got about a megabyte to give.  Storage size is not the issue.

    Copying structures is another matter.  It happens implicitly when you pass a structure instance as an argument to a method.  This is where the size of the cache-line gets relevant.  The code that the JIT compiler generates is not pretty btw, lots of MOV instructions.  There is a very simple workaround for this, pass the structure by reference.  ByRef in VB.NET, "ref" in C#.  The compiler now simply passes a pointer rather than copying every byte in the structure.

    My advice would be: start passing structures by reference once they start to get large.  Don't let it cramp your design otherwise.  If you think you have a problem, measure first.

    Wednesday, May 21, 2008 8:44 PM
    Moderator
  • It should also be noted that structs exist on the stack and there is a limited amount of stack space.  I beleive the default is 1 megabytes for a .net application.  The max stack size is stored in the pe header.  This can be changed using the editbin program that cames with visual c++.  Also like nobugs said when dealing with large struct you should pass them around by ref so you are just copying a 4 or 8 byte pointer to the struct and not the entire thing.  Here is a quick example to show why creating very large structs can be bad.  Uncommenting either of the lines below will cause stack overflow.

    Code Snippet

            static void Main(string[] args)
            {
                LargeStruct t = new LargeStruct();
             //  LargeStruct t2 = new LargeStruct();

                NoOverflow(ref t);
               // CauseStackOverflow(t);
            }

            private static void NoOverflow(ref LargeStruct t)
            {
            }
            private static void CauseStackOverflow(LargeStruct t)
            {
            }

            [StructLayout(LayoutKind.Sequential, Size = 524288)]
            public struct LargeStruct
            {
            }

    Wednesday, May 21, 2008 10:12 PM
  • Thank you all guys, your answers are very informative, however i still can't figure why is this 16 a magic number.
    FYI, I found this information in two books I assume authors know what they write:
    1. CLR via C# by Jeffrey Richter
    2. Framework Design Guidelines by Krzysztof Cwalina & Brad Abrams
    Here is a fragment from the first one:

    /.../ you should declare a type as a value type if one of the following statements is true:
    • Instances of the type are small (approximately 16 bytes or less)
    • Instances of the type are large (greater than 16 bytes) and are not passed as method parameters or returned from methods.
    Generally speaking, this is exactly what you wrote here ( and thank you for that!), but I just want to understand why 16 is the number?
    Thursday, May 22, 2008 1:51 PM
  • It's just the author's proposal for a structure that can still be considered small.  Four members.  Good choice.  Note the phrase "approximately".
    Thursday, May 22, 2008 2:53 PM
    Moderator
  • By the way, these recommendations would also depend on bitness (because on 64-bit you have instructions for copying more data around in a single instruction).  Additionally, in the SP1 JIT (on x86; x64 didn't have the limitation) methods with complex value type parameters can get inlined, thus reducing the overhead of parameter passing in many cases.

     

    So as nobugz said - this is just a number.

    Friday, May 23, 2008 6:06 AM