Questions About Allocations - Arrays - Structs and so on

Hi

About allocations, I have some questions.

If I understand correctly, structs doesn't cause memory allocations (in that matter that they have to be cleaned up by GC later).

However if I create an array of structs like `new float[100];` that would cause something like `100*4 (a float uses 4 bytes) = 400 bytes` of memory being allocated (perhaps a bit more though due to references and stuff), correct?

And let's say I create a class named MyClass, it contains a reference to MyClass2, however there exists no references to either MyClass or MyClass2 (except for the one in MyClass), but both classes will get collected anyway wont they?

Also, since structs are not causing allocations (if I am not wrong about that), would this code lead to better performance (memory or speed wise)?

float myVariable = 0;

for (int i=0;i < someNumber; i++) {
    myVariable = myArray _- mySecondArray*;*_
 _*//Do something with myVariable*_
_*}*_
_*```*_
_*<p>when compared to</p>*_
_*```*_
_*for (int i=0;i < someNumber; i++) {*_
 <em><em>float myVariable = myArray _- mySecondArray*;*_</em></em>
 <em><em>_*//Do something with myVariable*_</em></em>
<em><em>_*}*_</em></em>
<em><em>_*```*_</em></em>
<em><em>_*<p>Or is that just unnecessary coding?</p>*_</em></em>
<em><em>_*<p>Lastly, if I create an array (of structs, floats or Vector3s or whatever) and fill it with something, if I later refill that array with new values, will it result in more allocated memory. As I see it, it will only take the old values places in memory and therefore not result in any more allocated memory, am I right about that?</p>*_</em></em>
<em><em>_*<p>Thanks in advance!</p>*_</em></em>

However if I create an array of structs like new float[100]; that would cause something like 100*4 (a float uses 4 bytes) = 400 bytes of memory being allocated (perhaps a bit more though due to references and stuff), correct?

Yes. I think at least 408 bytes (guesstimate) since it require at least a pointer and a size (4 + 4). But probably more due to type information and other things.

Note that Array is a class though, it isn't a value type. It is a reference type. It contain several instances of values in your example though.

And let's say I create a class named MyClass, it contains a reference to MyClass2, however there exists no references to either MyClass or MyClass2 (except for the one in MyClass), but both classes will get collected anyway wont they?

Yes. The garbage collector will remove objects that have no other objects referencing them any longer. It doesn't matter of A point to B and B point to A. If neither A or B are connected to the running context, they are garbage.

  • Word of precaution! Remember that unity might internally keep references to your items! That means that you can't simply remove a reference on your end and be happy about it. This is sort of a memory leak, but it's called a memory pool. A simple example usually happen when you have registered as a listener to an event, and forget about removing it at a later time. You think you've removed all references to the object but forgot it is placed in an event list, so the object can't be collected. For all GC is concerned, your object must be important doing what it is doing and leaves it in memory. However, once the object that is responsible for the event is dereferenced, all pooled references are freed once the GC gets around working.

Also, since structs are not causing allocations (if I am not wrong about that), would this code lead to better performance (memory or speed wise)?

Structs can allocate items just as classes can. You can create explicit constructors that instantiate a reference typed object for example.

Or is that just unnecessary coding?

Likely unnecessary coding. If I was your senior I'd slap your nose for coding like that without a good reason. The very least it is premature optimization and premature optimization is the root of all evil. You dont want all evil in your project. If you want to be sure you can use tools such as IDLASM that will list IL assembly code (it's not THAT hard).

Lastly, if I create an array (of structs, floats or Vector3s or whatever) and fill it with something, if I later refill that array with new values, will it result in more allocated memory?

Reusing arrays is a good way of reducing allocations. If you have memory and time critical operations it's good to consider reusing arrays where possible. But remember that any optimization often leads to harder to maintain code, don't be over zealous.

As I see it, it will only take the old values places in memory and therefore not result in any more allocated memory, am I right about that?

Sounds about right.

Arrays are fast, slim and about the best way to store most simple data. Depending on the context though, arrays might not be the best candidate for storage. If you need to look up data very quickly for example you'd be better off with some sorted collection to perform binary search, or use hash sets if you have a very large collection. Also know that List etc are pretty damn nice and won't reallocate data stupidly. It'll allocate when it needs to, and it'll allocate enough to not allocate again shortly by expanding the capacity considerably. You can also set a default capacity to your lists if you don't expect to have a lot of items in them. Simply pass an integer to its constructor specifying the capacity you expect to have.


A word about premature optimization and how best used wrong.

(Or, how this answer went off topic)

Lastly. It seems you're pressing hard concerns on your performance. Are you sure performance is that critical? Will you save 50% of your CPU time or 0.5% of your CPU time? You have to benchmark it (Profile it) to be sure. Often it's the complexity of the operations you're making that consume time, not the time to make an operation, if you know what I mean. Focus on optimizing correct parts of your program. An example would be searching a collection if it contains the number 83. Simple enough, a novice might think.

// Naive test, with complexity O(N)
for (int i = 0; i < numbers.Length; ++i)
{
    if (numbers *== 83)*
 *return true;*
*}*
*return false;*
*```*
*<p>You can try and press that code down to molecules without getting any considerable performance gains:</p>*
*```*
*// Naive optimization attempt, still with complexity O(N)*
*int size = numbers.Length; // no need to query Array.Length every update*
*int i = 0; // maybe we save a few cycles if i is declared here?*
*for (; i < size; ++i)*
*{*
 _if (numbers *== 83)*_
 _*return true;*_
_*}*_
_*return false;*_
_*```*_
_*<p>But the gains are miniscule. You could go further unrolling the loop but you'll end up cluttering down the code without getting any leaps at all. Let's rethink the situation. What if the array was sorted to begin with? Let's say it would contain:</p>*_
_*<p>1, 4, 32, 33, 34, 83, 99</p>*_
_*<p>We can take advantage of the order to perform massive optimization (at least for larger collections) by divide and conquer (O(log N)). It works by checking halves of collections. So we want to see if 83 is in the collection. We know the size of the collection so we test what is in the middle of the collection.</p>*_
_*<p>1, 4, 32, <strong>33</strong>, 34, 83, 99</p>*_
_*<p>We got 33. Ah, it is not 83. 83 must probably then be in the <strong>upper half</strong> so we can effectively ignore <strong>half of the collection</strong>. Then you can repeat this procedure on the smaller collection so you're always working on half a collection, then half of sub collection, and so on;</p>*_
_*<p>34, <strong>83</strong>, 99</p>*_
_*<p>Bingo, we found 83. Yes, 83 is in the collection. We found it in only 2 tests. Compare to the naive for loop, which would clock in at 6 tests. In fact, the worst case for this approach would been 3 tests. The worst case for the for-loop approach would been 7 tests. This method really shines on large collections and can be implemented easily without having to reorder variables etc (and make the code unreadable).</p>*_
_*<p>To put some perspective, imagine searching through 256 items. Binary search would check at most 8 items. For loop would check at most 256 items. Imagine a list of 65536 items. Binary search: 16 tries. For loop: 65536 tries. Imagine a list of 4294967296 items. Binary search: 32 tries. For loop, you guessed it, 4294967296 tries. THAT is optimization.</p>*_
_*<p>Eh, seems I shat out a ton of text. Oh well.</p>*_