Why Is 16 Byte the Recommended Size for Struct in C#

Why is 16 byte the recommended size for struct in C#?

Only you know how your structs are being used in your program. But if nothing else, you can always test it for yourself. For instance, if it's frequently passed to other functions, the following may illuminate you:

class MainClass
{
static void Main()
{
Struct64 s1 = new Struct64();
Class64 c1 = new Class64();
DoStuff(s1);
DoStuff(c1);
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i < 10000; i++)
{
s1 = DoStuff(s1);
}
sw.Stop();
Console.WriteLine("Struct {0}", sw.ElapsedTicks);
sw.Reset();

sw.Start();
for (int i = 0; i < 10000; i++)
{
c1 = DoStuff(c1);
}
sw.Stop();
Console.WriteLine("Class {0}", sw.ElapsedTicks);
sw.Reset();

Console.ReadLine();
}
}

with:

public class Class64
{
public long l1;
public long l2;
public long l3;
public long l4;
public long l5;
public long l6;
public long l7;
public long l8;
}
public struct Struct64
{
public long l1;
public long l2;
public long l3;
public long l4;
public long l5;
public long l6;
public long l7;
public long l8;
}

Try this sort of thing with representative structs/classes, and see what results you get. (On my machine, above test, the class seems ~3 times faster)

Why should a .NET struct be less than 16 bytes?

It is just a performance rule of thumb.

The point is that because value types are passed by value, the entire size of the struct has to be copied if it is passed to a function, whereas for a reference type, only the reference (4 bytes) has to be copied. A struct might save a bit of time though because you remove a layer of indirection, so even if it is larger than these 4 bytes, it might still be more efficient than passing a reference around. But at some point, it becomes so big that the cost of copying becomes noticeable. And a common rule of thumb is that this typically happens around 16 bytes. 16 is chosen because it's a nice round number, a power of two, and the alternatives are either 8 (which is too small, and would make structs almost useless), or 32 (at which point the cost of copying the struct is already problematic if you're using structs for performance reasons)

But ultimately, this is performance advice. It answers the question of "which would be most efficient to use? A struct or a class?". But it doesn't answer the question of "which best maps to my problem domain".

Structs and classes behave differently. If you need a struct's behavior, then I would say to make it a struct, no matter the size. At least until you run into performance problems, profile your code, and find that your struct is a problem.

your link even says that it is just a matter of performance:

If one or more of these conditions are
not met, create a reference type
instead of a structure. Failure to
adhere to this guideline can
negatively impact performance.

Does the C# 16 Byte struct limitation mean I can't use strings in structs?

Advice regarding the 16-byte threshold should be considered out of date. The .NET Runtime optimizes operations on structures below a certain size (which used to be 16 bytes, but has since increased to I think 24); the cost per byte increases when structures exceed that size, but that doesn't mean one should use a class for everything larger than that.

The cost of creating a new structure is proportional to its size. The cost of creating a new class object is proportional to its size plus some significant additional overhead. The cost of copying an existing structure is proportional to its size, with a penalty for structs over a certain size. The cost of copying a reference to an immutable class object is fixed and small.

Creating a structure of any size and copying it once or twice will always be cheaper than creating a class object of the same size and copying the reference likewise. If the structure or class reference would get copied millions of times, even a 12-byte class might outperform a 12 byte structure. For values in between, the break-even size for class vs. structure will depend upon the number of times a struct or class reference would get copied, but unless a type is really big or would be copied many thousands of times, the extra cost to copy structures will still be cheaper than the extra cost to create class objects.

With regard to the fact that a structure contains strings, one should regard a reference to an immutable-type object as having a cost of 4 or 8 bytes on a 32-bit or 64-bit system. Since a string field does not "hold" a string but merely identifies one, a struct with two string fields would thus take 8 or 16 bytes, regardless of the lengths of the strings identified thereby.

Pad a struct so its size is a multiple of 16 bytes

After more searching, it indeed does look like I have to manually set the Size and FieldOffset to get to the right packing rules for Constant Buffers in DirectX/HLSL/C# interop code.

In my own project this is even a bit more complex. Since I use source generators to create these structs. But in the end I was able to figure it out. For those interested the source code can be found here.

Optimal solution for struct with more than 16 bytes

It depends how you are going to use it?

  1. Are you going to allocate a lot of it vs. pass it around a lot?
  2. Is it going to be consumed by 3rd party code? In this case, classes typically give you more flexibility.
  3. Do you want struct vs. class semantics? For example, non-nullable?
  4. Would you benefit from having a couple of pre-created instances of this class that can be re-use to represent special cases? Similar to String.Empty. In this case you would benefit from a class.

It is hard to answer just from the information you provided in your question.

NET] Why is struct better with being less than 16 bytes

It's because 16 bytes is the threshold where the compiler starts copying structs as block of memory instead of using one or two simple move instructions.

The compiler optimises the copying of structures when they are small. A struct that is for example eight bytes can be copied as a single 64 bit value. A struct that is 16 bytes can be copies as one or two singular values (depending on the processor architecture). When the structure is larger than 16 bytes, the compiler doesn't try to optimise the move any more, and the fallback is to call a method that copies a block of memory.

(Note: the threshold of 16 bytes may differ depending on the version of compiler, it seems as it actually tries to optimise beyond that point in newer versions, but the optimised code will still be a lot of move instructions, compared to copying a reference to an object which still is a single move operation.)

Edit:

Here is the result of a test that I did on my 64 bit system copying structs half a billion times:

struct 4    : 272 ms.
struct 8 : 235 ms.
struct 16 : 317 ms.
struct 32 : 625 ms.
struct 64 : 1280 ms.
struct 128 : 4659 ms.
struct 256 : 8020 ms.

As you see, below 16 bytes the time is not linear, although 16 bytes is four times as much as 4 bytes, it doesn't take four times longer. Above 16 bytes the time is linear, so doubling the size double the time. That's where it would start using multiple moves. Above 64 bytes there is a jump, where the time suddenly quadruples when the size doubles. That's where the fallback would start to be used.

Why is KeyValuePairint, long 16 bytes?

It's due to data structure Alignment. To quote the Wikipedia article:

Data alignment means putting the data at a memory address equal to some multiple of the word size, which increases the system's performance due to the way the CPU handles memory. To align the data, it may be necessary to insert some meaningless bytes between the end of the last data structure and the start of the next, which is data structure padding.

As an int is 4 bytes having a KeyValuePair of a long and an int would result in misaligned data structures. This would result in a significant drop in performance. It's therefore better to "waste" 4 bytes on each record to get the data aligned and therefore able to be written and read efficiently.

As to why single ints aren't padded - don't forget with a structure/class it's relatively easy to add padding variable - but for a plain value type you haven't got that structure/class there. I'd guess that the extra work required (adding a dummy structure around the int to insert the padding into) isn't worth the performance gain.

There's real no difference between 32 bit and 64 bit operating systems here, they'll still need padding and aligning, but the amount of padding might vary.

In the "old days" you often had to manually add padding to data structures to get the correct alignment, these days it tends to be handled automatically by the compiler.

Why is the minimum size of a reference type 12 bytes for a 32 bit .NET process

Minimum of 12 bytes is a requirement of the garbage collection implementation.

From here: http://msdn.microsoft.com/en-us/magazine/cc163791.aspx#S9

The Base Instance Size is the size of the object as computed by the class loader, based on the field declarations in the code. As discussed previously, the current GC implementation needs an object instance of at least 12 bytes. If a class does not have any instance fields defined, it will carry an overhead of 4 bytes. The rest of the 8 bytes will be taken up by the Object Header (which may contain a syncblk number) and TypeHandle.

(TypeHandle being a handle to the method table).



Related Topics



Leave a reply



Submit