1

I was reviewing some code, and they basically had:

while (true) // update loop
{
   someStruct = new SomeStruct(1,2);
}

Creating objects like that in a loop is bad, you should create it once and then update it to avoid creating/destroying millions of them.

But this isn't a problem:

while (true) // update loop
{
   x = x + 1;
}

Since x is a value type. But then structs are also kind of similar to value types. They are apparently not subject to garbage collection.

So now I'm wondering if there's any performance cost with creating structs in a loop or if you should still create one and update it (code readability/future proofing aside).

NibblyPig
  • 51,118
  • 72
  • 200
  • 356
  • 1
    Related: https://stackoverflow.com/questions/33671215/what-is-the-memory-overhead-of-a-net-custom-struct-type – Matt Evans May 19 '21 at 08:24
  • 1
    It depends on whether the struct contains any reference types. If not, then the whole thing will live on the stack (in your example code). – Matthew Watson May 19 '21 at 08:25
  • 1
    If `someStruct` is actually a struct, and the constructor of it doesn't allocate any new objects that live on the heap (aka reference types), then that is actually not that different from the `x = x + 1` statement. – Lasse V. Karlsen May 19 '21 at 08:29
  • I sense an inbound Skeet – Matt Evans May 19 '21 at 08:30
  • 1
    Mutable struct is root of ... yes... Jon Skeet is coming for you for even thinking to suggest mutable structs :) – Alexei Levenkov May 19 '21 at 08:32
  • Hmmm. I am reading things here that are new to me. What if the struct does contain members of reference types? Does it really matter? As far as I could tell, when a structure goes out of scope, any objects in the heap that it references *could* become "orphaned" and if so, those objects will be cleaned up by the garbage collector as well. Or am I seriously mistaken here? (Note that I am not discussing disposable objects here that might leave resources in use when not explicitly disposed correctly.) – Bart Hofland May 19 '21 at 09:07
  • @BartHofland: You are correct. What those users probably meant was: *If* your struct contains members of reference types and *if* you create instances of those reference types in your struct constructor to assign them to your members (rather them leaving them at their default value of `null`), *then* unnecessarily re-creating that struct will create (and abandon) objects on the heap and, thus, incur a performance penalty (both during object creation as well as later during garbage collection). – Heinzi May 24 '21 at 16:12

2 Answers2

3

It will probably be optimized away by the JITter. Consider the following example,

private struct SomeStruct
{
    public int A;
    public SomeStruct(int a) { A = a; }
}

public void M() {
    SomeStruct s = new SomeStruct(1);
    
    while (true) // update loop
    {
       s = new SomeStruct(s.A + 1);
    }
}

where method M, according to sharplab, is JITted to:

L0000: push ebp
L0001: mov ebp, esp
L0003: mov eax, 1
L0008: inc eax
L0009: jmp short L0008

Replacing the loop with

while (true) // update loop
{
   s.A = s.A + 1;
}

yields exactly the same assembly code.


Thus, as a conclusion, I would recommend to use the following code (same as the first example, just made the struct field readonly):

private struct SomeStruct
{
    public readonly int A;
    public SomeStruct(int a) { A = a; }
}

public void M() {
    SomeStruct s = new SomeStruct(1);
    
    while (true) // update loop
    {
       s = new SomeStruct(s.A + 1);
    }
}

It creates the same assembly code as the "mutable" struct, but you have the additional safety of using an "immutable" struct during compilation.

Heinzi
  • 167,459
  • 57
  • 363
  • 519
2

So now I'm wondering if there's any performance cost with creating structs in a loop

As always with performance; Measure! As you say they will not affect garbage collection, and I would not expect any significant performance problem. However, the c# jitter has historically been a bit limited in what optimizations it applies, so if performance if the most important concern it might help to make things easy for it and write as simple code as possible.

or if you should still create one and update it (code readability/future proofing aside).

I would argue that updating a struct in place is probably a bad idea. See why mutable structs are evil. But it might depend on how large structures you are using, if you for some reason have a very large struct and only need to update one value, it might be better to update it in place, and live with the problems of mutable structs. But you should avoid large structs for performance sensitive scenarios unless you understand all rules regarding value types and copying.

JonasH
  • 28,608
  • 2
  • 10
  • 23
  • If "you for some reason have a very large struct" it's your own fault of not reading guidance and really no one else should care about performance of your code at that point :) (Indeed it is possible there is a use case but author of such code is expected to perfectly understand all drawbacks at this point and should be able to easily explain all that during the code review) – Alexei Levenkov May 19 '21 at 08:34
  • @Alexei Levenkov there are some valid reasons for having large structs, for example if control over memory layout is important. But it should be rare, and will require very careful usage to avoid copies. – JonasH May 19 '21 at 08:39