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

7

37

I read the Cwalina book (recommendations on development and design of .NET applications).

He says that a good designed struct has to be less than 16 bytes in size (for performance purposes).

Why exactly is this?

And (more important) can I have larger struct with same efficiency if I run my .NET 3.5 (soon to be .NET 4.0) 64-bit application on Core i7 under Windows 7 x64 (is this limitation CPU / OS based)?

Just to stress again - I need as efficient struct as it is possible. I try to keep it on the stack all the time. The application is heavily multi-threaded and runs on sub-millisecond intervals, and the current size of the struct is 64 bytes.

Merridie answered 9/3, 2010 at 8:56 Comment(5)
What makes you believe that keeping your data on the stack is more efficient? Stack vs. heap is an implementation detail in .NET and developers shouldn't care (see #477601)Claudy
Asking about efficiency without saying which metrics you're using is a tad vague. It's like asking for the most efficient vehicle - without mentioning whether you're wanting to save money on your commute, or trying to transport 30 tonnes of cargo.Tammara
Divo you are wrong in principle. What should I care about to make my application better is outside of this topic. ThanksMerridie
@maxima120: My point was not to criticize you personally, but what is more important in .NET than stack vs. heap is value type semantics vs. reference type semantics. And having your variables on the stack doesn't mean "more efficient". Having a value type, however, could mean a negative impact as they are copied when passed along. Of course, my above comment is not an answer to your main question, but still a relevant aspect (That's why I didn't post it as answer). There are IMHO very few reasons to use structs in C# (e.g. P/Invoke, or when dealing with "values" such as DateTime).Claudy
See also #1082811Seigneury
T
11

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)

Tammara answered 9/3, 2010 at 9:12 Comment(1)
Replace the benchmark with a loop that for a struct does for (int i=0; i<100000; i++) {mystruct.l1 += i;}, and then try it for a struct and class with for (int i=0; i<100000; i++) {mything = new Whatever(mything.l1+i, mything.l2, mything.l3, mything.l4, mything.l5, mything.l6, mything.l7, mything.l8)}; The simple mutable struct will win by a mile, with an execution time that's independent of the number of fields. The "immutable-struct-style" version is apt to outperform the class version by a considerable margin, however; adding dozens of long fields would not change that.Unlettered
A
24

You're misquoting the book (at least the 2nd edition). Jeffrey Richter states value types can be more than 16 bytes if:

You don't intend to pass them to other methods or copy them to and from a collection class.

Additionally Eric Gunnerson adds (regarding the 16 byte limit)

Use this guideline as a trigger to do more investigation.

It is simply not true that a struct "has to be less than 16 bytes in size". It all depends on usage.

If you are creating the struct and also consuming it and are worried about performance then use a profiler comparing a struct vs class to see which works best for you.

Ataliah answered 9/3, 2010 at 9:4 Comment(2)
I said - it has to be to be considered a good design. Please quote the whole sentence. Cos from how you distored my words is - struct has to be 16 bytes or less... No it has not! I read this book just yesterday. I know what Richter said and this is not applicable to my application hence I simply ignored this... Because the topic is NOT to discuss the book but help me make MY application more efficient Thank you very much! And as for Gunnerson - that is EXACTLY what I am trying to do here - investigate further!Merridie
@Maxima120. I didn't distort your words. You misquoted the book, I corrected you. The book does state to avoid structs that don't have an instance size under 16 bytes (then adds caveats). That is clearly not the same as a "good designed struct has to be less than 16 bytes in size".Ataliah
T
11

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)

Tammara answered 9/3, 2010 at 9:12 Comment(1)
Replace the benchmark with a loop that for a struct does for (int i=0; i<100000; i++) {mystruct.l1 += i;}, and then try it for a struct and class with for (int i=0; i<100000; i++) {mything = new Whatever(mything.l1+i, mything.l2, mything.l3, mything.l4, mything.l5, mything.l6, mything.l7, mything.l8)}; The simple mutable struct will win by a mile, with an execution time that's independent of the number of fields. The "immutable-struct-style" version is apt to outperform the class version by a considerable margin, however; adding dozens of long fields would not change that.Unlettered
U
10

The JIT compiler produces special-case code to copy structures which are smaller than a certain thresholds, and a somewhat-slower general-purpose construct for larger ones. I would conjecture that when that advice was written, the threshold was 16 bytes, but in today's 32-bit framework it seems to be 24 bytes; it may be larger for 64-bit code.

That having been said, the cost of creating any size class object is substantially greater than the cost of copying a struct which holds the same data. If code creates a class object with 32 bytes worth of fields and then passes or otherwise copies a reference to that object 1,000 times, the time savings from copying 1,000 object references instead of having to copy 1,000 32-byte structures would likely outweigh the cost of creating the class object. If, however, the object instance would be abandoned after the reference has been copied only twice, the cost of creating the object would probably exceed by a large margin the cost of copying a 32-byte structure twice.

Note also that it's possible in many cases to avoid passing around structures by value or otherwise redundantly copying them, if one endeavors to do so. Passing any size of structure as a ref parameter to a method, for example, only requires passing a single-machine-word (4 or 8 bytes) address. Because .net lacks any sort of const ref concept, only writable fields or variables may be passed that way, and the collections built into .net--other than System.Array--provide no means to access members by ref. If one is willing to use arrays or custom collections, however, even huge (100 bytes or more) structures can be processed very efficiently. In many cases, the performance advantage of using a structure rather than an immutable class may grow with the size of the encapsulated data.

Unlettered answered 13/3, 2013 at 20:26 Comment(0)
R
5
  • Larger struct is not as efficient, but then.... if you HAVE more data, you have it. No sense talking about efficiency there.

  • 64 bytes should be OK.

  • The main reason possibly is copy operations.... which get IIRC slower if the struct is larger. And it must be copied around quite a lot.

I would normally advice into using a class here ;) But without knowing the content of the struct, it is a little tricky.

Rhapsodize answered 9/3, 2010 at 8:58 Comment(3)
Tom thanks mate. Could you please provide some information which outlines why 64 bytes is OK for my CPU ? BTW - the struct was natural choice for avoiding locks. The cost of development will go much higher if I used immutable class or locks instead... So lets pass on class vs struct..Merridie
Well, there is no reference. I jus tthink 64 bytes may not be too much. It REALLY depends on the data that is in there. I NORMALLY dont use structs like that. I have a financial trading app (tons of data going through - little, around 64 bytes, somtiems 50.000+ items per second coming in) and I use classes.Rhapsodize
I might be wrong about struct vs class... It could be worth to look into it.Merridie
A
3

One of the things I learnt while doing assembly language programming was that you align your structs (and other data) on 8 or 16 byte boundaries (there is actually a preprocessor command for it in MASM). The reason for this is considerably faster memory access when storing or moving things around in memory. Keeping your structs under 16 bytes in size would ensure that this memory alignment could be done successfully because nothing would cross the alignment boundaries.

However I would expect this sort of thing to be largely hidden from you when using the .NET framework. If your structs were bigger than 16 bytes then I would expect the framework (or JIT compiler) to align your data on 32 byte boundaries, and so on. It would be interesting to see some validation of the comments in those books, and see what the difference in speed is.

Assiduous answered 9/3, 2010 at 9:25 Comment(2)
the layout of structs is not a secret. Look up for LayoutStruct C# attribute.. E.g. vsj.co.uk/articles/display.asp?id=501Merridie
That is a good link. I was more talking about the behaviour of the framework/JITter, and wondering why 16 was such a magic number (and whether it is still so magic or whether it has either been increased or made irrelevant).Assiduous
E
3

Actually, on a 64-bit machine 32 bytes is the maximum size to use for struct data. From my tests 32 byte struct (four longs) performs about as well as an object reference.

You can find test code and run the tests yourself here: https://github.com/agileharbor/structBenchmark

The last couple of tests copy a large number of structs/objects from one array to another, and you're able to see where class outperforms struct with eight longs, but it is about the same as struct with four longs.

Encephalic answered 23/6, 2016 at 1:33 Comment(0)
F
2

I think another key point is that if you have a list with a million structs, that is still only one memory allocation on the heap, while a list with a million classes in it is going to have about a million separate heap objects. The garbage collection load is something quite different in both cases. From this point of view, the struct may come out ahead.

Ful answered 9/3, 2010 at 9:21 Comment(4)
if you store you're structs in a heap allocated object you better have them heap allocated as well! otherwise things get really funny when your current stackframe dies (together with the stack allocated objects) and you try to access them through the heap allocated object. That's one reason why all members (including value typed) are stored on the heapStenger
@Rune FS - true, but the whole vector of structs is going to be in one large, contiguous block of memory. As opposed to a million small pieces with individual lifetimes.Ful
This is true with a node-based list implementation. However, System.Collections.Generic.List<T> is internally backed by a T[] which resizes as needed.Hawthorn
@KyleBaran Arrays of reference types don't allocate the objects they refer to in contiguous blocks of memory, and the objects they refer to don't have uniform lifetimes. Rather, arrays of reference types allocate a contiguous block of references to objects that are allocated individually wherever the GC managed memory sees fit and that are garbage collected by exactly the same rules as any other reference type objects are, whenever the GC sees fit.Janka

© 2022 - 2024 — McMap. All rights reserved.