5

I'm creating a C# console-app. I have some critical paths and thought that creating structs would be faster than creating classes since I would not need garbage collection for structs. In my test however I found the opposite.

In the test below, I create 1000 structs and 1000 classes.

class Program
{
    static void Main(string[] args)
    {
        int iterations = 1000;
        Stopwatch sw = new Stopwatch();

        sw.Start();
        List<Struct22> structures = new List<Struct22>();
        for (int i = 0; i < iterations; ++i)
        {
            structures.Add(new Struct22());
        }
        sw.Stop();
        Console.WriteLine($"Struct creation consumed {sw.ElapsedTicks} ticks");

        Stopwatch sw2 = new Stopwatch();
        sw2.Start();
        List<Class33> classes = new List<Class33>();
        for (int i = 0; i < iterations; ++i)
        {
            classes.Add(new Class33());
        }
        sw2.Stop();
        Console.WriteLine($"Class creation consumed {sw2.ElapsedTicks} ticks");


        Console.ReadLine();
    }
}

My classe / struct are simple:

class Class33
{
    public int Property { get; set; }
    public int Field;
    public void Method() { }
}

struct Struct22
{
    public int Property { get; set; }
    public int Field;
    public void Method() { }
}

Results (drum roll please...)

Struct creating consuming 3038 ticks
Class creating consuming 404 ticks

So the question is: Why would it take close to 10x the amount of time for a Class than it does for a Struct ?

EDIT. I made the Program "Do something" by just assigning integers to the properties.

 static void Main(string[] args)
    {
        int iterations = 10000000;
        Stopwatch sw = new Stopwatch();

        sw.Start();
        List<Struct22> structures = new List<Struct22>();
        for (int i = 0; i < iterations; ++i)
        {
            Struct22 s = new Struct22()
            {
                Property = 2,
                Field = 3
            };
            structures.Add(s);
        }
        sw.Stop();
        Console.WriteLine($"Struct creating consuming {sw.ElapsedTicks} ticks");

        Stopwatch sw2 = new Stopwatch();
        sw2.Start();
        List<Class33> classes = new List<Class33>();
        for (int i = 0; i < iterations; ++i)
        {
            Class33 c = new Class33()
            {
                Property = 2,
                Field = 3
            };
            classes.Add(c);
        }
        sw2.Stop();
        Console.WriteLine($"Class creating consuming {sw2.ElapsedTicks} ticks");


        Console.ReadLine();
    }

and the result is astounding to me. Classes are still at least 2x but the simple fact of assigning integers had a 20x impact!

Struct creating consuming 903456 ticks
Class creating consuming 4345929 ticks

EDIT: I removed references to Methods so there are no reference types in my Class or Struct:

class Class33
{
    public int Property { get; set; }
    public int Field;
}

struct Struct22
{
    public int Property { get; set; }
    public int Field;
}
Ed Landau
  • 966
  • 2
  • 11
  • 24
  • 4
    You're not actually doing anything meaningful though. Measuring doing nothing doesn't actually tell you anything. – Servy Dec 01 '16 at 16:17
  • Structs vs. classes is a long-lived debate. You'll need to measure the part that's truly a critical path to find the problem. [cf this discussion](http://stackoverflow.com/questions/3942721/structs-versus-classes) – D. Ben Knoble Dec 01 '16 at 16:19
  • 5
    [These](https://msdn.microsoft.com/en-us/library/ms229017.aspx) are the things you should consider when you think about using structs or classes: _"usea struct instead of a class if instances of the type are **small** and commonly **short-lived** or are commonly embedded in other objects. X AVOID defining a struct unless the type has all of the following characteristics: It logically represents a **single value**, similar to primitive types (int, double, etc.). It has an instance size under 16 bytes. It is **immutable**. It will not have to be boxed frequently."_ – Tim Schmelter Dec 01 '16 at 16:25
  • 1
    Apart from that every reference type in a struct is garbage collected too. – Tim Schmelter Dec 01 '16 at 16:27
  • 1
    go for `10000000` to see the real difference. `1000` is so small amount and finishes really fast that anything can affect results specially jitter. BUT don't use structs because they are faster. – M.kazem Akhgary Dec 01 '16 at 16:27
  • @TimSchmelter No. If the struct contains a reference type that that reference type would be garbage collected. The struct containing it would not be managed by the GC. – Servy Dec 01 '16 at 16:28
  • I went for 1000000 and it stayed the same Akhgary. Also, Tim, I removed my reference to a Method so now my Class and Struct just have two integers. No difference. – Ed Landau Dec 01 '16 at 16:31
  • 2
    You are making all the traditional benchmark mistakes. Easy to see by swapping the class and the struct test or (better) just by repeating the test 10 times. You'll then see that the struct is about twice as fast. Managed code execution and memory allocation is best compared to a freight train taking its time to get to 200 mph. – Hans Passant Dec 01 '16 at 17:59

3 Answers3

2

The performance difference can probably (or at least in part) be explained by a simple example.

For structures.Add(new Struct22()); this is what really happens:

  • A Struct22 is created and intialized.
  • The Add method is called, but it receives a copy because the item is a value type.

So calling Add in this case has overhead, incurred by making a new Struct22 and copying all fields and properties into it from the original.


To demonstrate, not focusing on speed but on the fact that copying takes place:

private static void StructDemo()
{
    List<Struct22> list = new List<Struct22>();

    Struct22 s1 = new Struct22() { Property = 2, Field = 3 };  // #1
    list.Add(s1);                            // This creates copy #2
    Struct22 s3 = list[0];                   // This creates copy #3

    // Change properties:
    s1.Property = 777;
    // list[0].Property = 888;    <-- Compile error, NOT possible
    s3.Property = 999;

    Console.WriteLine("s1.Property = " + s1.Property);
    Console.WriteLine("list[0].Property = " + list[0].Property);
    Console.WriteLine("s3.Property = " + s3.Property);
}

This will be the output, proving that both Add() and the use of list[0] caused copies to be made:

s1.Property = 777
list[0].Property = 2
s3.Property = 999

Let this be a reminder that the behaviour of structs can be substantially different compared to objects, and that performance should be just one aspect when deciding what to use.

Peter B
  • 22,460
  • 5
  • 32
  • 69
0

As commented, deciding on struct vs class has many considerations. I have not seen many people concerned with instantiation as it is usually a very small part of the performance impact based on this descision.

I ran a few tests with your code and found it interesting that as the number of instances increases the struct is faster.

I cant answer your question as it appears that your assertion is not true. Classes do not always instantiate faster than Structs. Everything I have read states the opposite, but your test produces the interesting results you mentioned.

There are tools you can use to really dig in and try to find out why you get the results you do.

10000 
Struct creation consumed 2333 ticks
Class creation consumed 1616 ticks

100000
Struct creation consumed 5672 ticks
Class creation consumed 8459 ticks

1000000
Struct creation consumed 73462 ticks
Class creation consumed 221704 ticks
Joe C
  • 3,925
  • 2
  • 11
  • 31
  • Hi Joe. I agree. I went big as well until I ran out of memory and found that classes are still faster. I apologize if my test is not representative. Usually people ask questions on this forum and the answer usually is: TRY IT. So I did. Again, apologies if my test is not "representative". I now understand why and will modify my tests. – Ed Landau Dec 01 '16 at 16:34
0

List<T> stores T objects in internal Array.

Each time when the limit of capacity is reached, new double sized internal array is created and all values from old array are copied ...

When you create an empty List and try to populate it 1000 times, internal array recreated and copied about 10 times.

So in Your example classes could create slower, but each time when new array is created, List should copy only references to objects in case of List of Class, and all structure data in case of List of Struct ...

Try to create List with initial capacity initialized, for your code it should be:

new List<Struct22>(1000)

in this case internal array wont be recreated and structure case will work much faster