1

Introduction to the goal:

I am currently trying to optimize performance and memory usage of my code. (mainly Ram bottleneck)

The program will have many instances of the following element at the same time. Especially when historic prices should be processed at the fastest possible rate. The struct looks like this in it's simplest way:

public struct PriceElement
{
    public DateTime SpotTime { get; set; }
    public decimal BuyPrice { get; set; }
    public decimal SellPrice { get; set; }
}

I realized the performance benefits of using the struct just like an empty bottle and refill it after consumption. This way, I do not have to reallocate memory for each single element in the line.

However, it also made my code a little more dangerous for human errors in the program code. Namely I wanted to make sure that I always update the whole struct at once rather than maybe ending up with just an updated sellprice and buyprice because I forgot to update an element.

The element is very neat like this but I have to offload methods into functions in another classes in order to have the functionality I require - This in turn would be less intuitive and thus less preferable in code.

So I added some basic methods which make my life a lot easier:

public struct PriceElement
{
    public PriceElement(DateTime spotTime = default(DateTime), decimal buyPrice = 0, decimal sellPrice = 0)
    {
        // assign datetime min value if not happened already
        spotTime = spotTime == default(DateTime) ? DateTime.MinValue : spotTime;
        this.SpotTime = spotTime;
        this.BuyPrice = buyPrice;
        this.SellPrice = sellPrice;

    }
    // Data
    public DateTime SpotTime { get; private set; }
    public decimal BuyPrice { get; private set; }
    public decimal SellPrice { get; private set; }
    // Methods
    public decimal SpotPrice { get { return ((this.BuyPrice + this.SellPrice) / (decimal)2); } }
    // refills/overwrites this price element
    public void UpdatePrice(DateTime spotTime, decimal buyPrice, decimal sellPrice)
    {
        this.SpotTime = spotTime;
        this.BuyPrice = buyPrice;
        this.SellPrice = sellPrice;
    }
    public string ToString()
    {
        System.Text.StringBuilder output = new System.Text.StringBuilder();
        output.Append(this.SpotTime.ToString("dd/MM/yyyy HH:mm:ss"));
        output.Append(',');
        output.Append(this.BuyPrice);
        output.Append(',');
        output.Append(this.SellPrice);
        return output.ToString();
    }
}

Question:

Let's say I have PriceElement[1000000] - will those additional methods put additional strain on the system memory or are they "shared" between all structs of type PriceElement?

Will those additional methods increase the time to create a new PriceElement(DateTime, buy, sell) instance, respectively the load on the garbage collector?

Will there be any negative impacts, I have not mentioned here?

julian bechtold
  • 1,875
  • 2
  • 19
  • 49
  • 1
    No. However, consider whether you want to make that a class anyway. Microsoft recommend that structs have a [max size of 16 bytes](https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/choosing-between-class-and-struct), and yours is a whopping 40 bytes, so any saved cost in allocation is *probably* dwarfed by the cost of copying it around the place. If you *really* care about efficiency, and care enough to make it a struct rather than a class, you need to be profiling – canton7 Jan 07 '21 at 14:54
  • 3
    changing to class decreases code performance by a factor of >2.x - 4.x - after trial and error and testing, the struct came out way, way superior – julian bechtold Jan 07 '21 at 14:57
  • 2
    Good, glad you're profiling :) – canton7 Jan 07 '21 at 14:59
  • 1
    especially with the ref keyword, there is not much copying around. But thank you for the suggestion. – julian bechtold Jan 07 '21 at 15:00
  • 3
    Code exists only once in memory, while the data members are replicated once per instance and that's what drives the memory requirements of the struct. Methods, properties, constructors and all such code won't affect memory requirements of the program. – Alejandro Jan 07 '21 at 15:02
  • 1
    You can actually do `this = default` inside the instance method of a struct, that will clear the whole struct – Charlieface Jan 07 '21 at 15:15
  • Does this answer your question? [How would the memory look like for this object?](https://stackoverflow.com/questions/58382702/how-would-the-memory-look-like-for-this-object) –  Jan 08 '21 at 01:28
  • @OlivierRogier no, that is about the general memory "layout" of an object. Neither is it on structs, nor about memory duplication. But thank you for the suggestion. – julian bechtold Jan 08 '21 at 07:22
  • @julianbechtold Yep. What is right for one is right for many. *Each instance of a class or struct has a "personal memory space" for data, but methods are shared once for all objects* (memory for data and code is the same for any type of class or struct). –  Jan 08 '21 at 08:34

1 Answers1

2

will those additional methods put additional strain on the system memory or are they "shared" between all structs of type PriceElement?

Code is shared between all instances. So no additional memory will be used.

Code is stored separately from any data, and the memory for the code is only dependent on the amount of code, not how many instance of objects there are. This is true for both classes and structs. The main exception is generics, this will create a copy of the code for each type combination that is used. It is a bit more complicated since the code is Jitted, cached etc, but that is irrelevant in most cases since you cannot control it anyway.

I would recommend making your struct immutable. I.e. change UpdatePrice so it returns a new struct instead of changing the existing one. See why is mutable structs evil for details. Making the struct immutable allow you to mark the struct as readonly and that can help avoid copies when passing the struct with an in parameter. In modern c# you can take references to structs in an array, and that also helps avoiding copies (as you seem to be aware of).

JonasH
  • 28,608
  • 2
  • 10
  • 23
  • Hey Jonas, thank you for the great Answer! – julian bechtold Jan 08 '21 at 07:19
  • @julianbechtold Or to rephrase what is meant in that last paragraph: If the struct is not mutable, the JIT can replace `in` parameters with `ref`, knowing that the pointer cannot be mutated. This prevents copying for large structs, and effectively makes them stack pointers. And you can do the same thing with an array. – Charlieface Jan 08 '21 at 09:00