2

Lets say I have several classes like these:

class MyClassA {
    public List<string> UpData;
    public List<string> DownData;
    public List<string> LeftData;
    public List<string> RightData;
}

class MyClassB {
    public List<string> UpData;
    public List<string> DownData;
    public List<string> LeftData;
    public List<string> RightData;
}

I'd like to refactor them so I can add some conveniences (like an Enumerator, or having some functions that let me pick a specific member):

struct DirectionMap {
    public List<string> Up;
    public List<string> Down;
    public List<string> Left;
    public List<string> Right;
    //... various helper functions ...
}

class MyClassA {
    public DirectionMap Data;
}

class MyClassB {
    public DirectionMap Data;
}

However, my application is very memory intensive, and I have millions of instances of these, so I need to keep memory usage and garbage collection in mind. I also have to mutate these objects as often as possible, so speed is important too. Is there any performance overhead to doing this?

If I understand correctly, if DirectionMap were a class, it would use a bit more memory to store the pointer to it, and touch the heap an extra time, right? But is it free as a struct?

I understand that mutable objects in structs can cause mistakes, but I don't plan on ever passing Data around, just the string lists it contains, which are reference values.

ProgrammingLlama
  • 36,677
  • 7
  • 67
  • 86
lifeformed
  • 525
  • 4
  • 16
  • you have a list of strings, which would obviously go into the heap, don't they? – Saravanan Apr 28 '20 at 06:08
  • 1
    Yes, but the `DirectionMap` itself doesn't, right? I just wanted to know if there's anything else I'm missing. – lifeformed Apr 28 '20 at 06:11
  • Could you post one helper method of the `DirectionMap` struct as an example? – Theodor Zoulias Apr 28 '20 at 08:02
  • For example, I could implement `IEnumerable>` with basically `GetEnumerator() { yield return UpData; yield return DownData; yield return LeftData; yield return RightData; }`, so I can iterate over the values. – lifeformed Apr 28 '20 at 08:16
  • Or I could have a function take in a `Direction` enum and return the desired member. – lifeformed Apr 28 '20 at 08:17
  • Your structs are still allocated on heap. Also if you pass struct as parameters, the compiler creates defensive copy which could be a drawback affecting performance. Performance wise maybe benchmark test your solutions. – weichch Apr 28 '20 at 08:55

1 Answers1

2

The first rule when talking about performance is to profile the code, even experienced developers may be surprised at what things take time.

I would expect the memory usage and performance to be identical if you put things in a struct vs directly in a class. The memory layout should be similar or identical.

I would not recommend mutating the struct unless you are very careful. I.e. replacing the contents of the Up-list would be fine, but replacing the Up-list with a new list could cause issues. See Why mutating structs are evil.

If you implement interfaces for your struct you need to be aware of boxing. If you implement IEnumerable<List<string>> and use any linq method this would cause the struct to be boxed, not ideal for performance. This should be possible to avoid by using generic constraints. You might also want to use a readonly struct and the and the in parameter modifier to avoid copies . For example :

public static void MyMethod<T, U>(in T value) where T : struct, IEnumerable<U>
{
        // do something
}
JonasH
  • 28,608
  • 2
  • 10
  • 23
  • I don't thing that the OP intends to mutate the structs themselves. Their intention is to mutate the objects that are stored in the structs. Your suggestion of making the structs [`readonly`](https://learn.microsoft.com/en-us/dotnet/csharp/write-safe-efficient-code#declare-readonly-structs-for-immutable-value-types) is a great suggestion IMHO, in order to enforce these intentions. – Theodor Zoulias Apr 28 '20 at 10:52
  • Thanks. Does using `foreach` on an `IEnumerable`cause boxing? Also, is there a way to use `readonly` on a generic struct, like if I had `DirectionMap` with `List Up;` instead? The compiler says I need Ts to be readonly, but I don't see a way to constrain it. – lifeformed Apr 28 '20 at 11:13
  • using foreach should not cause boxing as far as I know. Using generics in a readonly struct should be fine, but all fields need to be marked as readonly. – JonasH Apr 28 '20 at 11:59