It doesn’t. Vector3 is a struct, which is a value type. It is stored on stack, and there’s no garbage generated.
Reference types generate garbage when you create them. And even in this case GC only needs to work when you RELEASE them. In case of “value types” new is a semantic quirk that does not mean an allocation.
There are value types and reference types.
Reference types are allocated on the heap. And cause allocation.
Value types are stored on the stack. And do not cause allocations.
However reference types can contain value types and value types can contain reference types.
A “new” struct creates no allocation only if it doesn’t create any value types in its constructor.
So, if your struct stores a class or a string in its fields, it will cause allocation for those members when initialized.
Color, Vector3 and many other types in unity are pure value types with no reference type members. So they cause no allocations.
Note that if you’re really into micro-optimization, you have to know that while ‘new Vector3(my_x,my_y, my_z);’ will not disturb the garbage collector, it will call the ‘Vector3’ constructor, so some code will be executed.
When doing a lot of Vector3 operations, it is often (as in '99% of the time) better to do it ‘per axis’.
For example:
v2 = v2 + v3
It is quite commonly used and yet, this is faster:
v2.x = v2.x + v3.x
v2.y = v2.y + v3.y
v2.z = v2.z + v3.z
It is faster because the previous line will in fact call the ‘Vector3’ constructor, while these 3 lines will not.
Of course, it has the disadvantages of being a lot less readable, and a lot more error-prone.
So, only do it when it really matters.
And as always with optimization: profile, profile, profile, etc…, profile again and again…
The number 1 priority of any code is its readability, and with modern computational power trying to avoid a function call is premature optimization in almost all cases.
This kind of thing should ONLY be done if profilers points at it as bottleneck, and otherwise it should never be even considered.
The constructor is a function call(*). Same goes for the Vector3 operators (+, -, *, etc): they’re all functions under the hood.
Function calls are quite cheap, but not free since it must push a stack, jump, pop the stack, and jump back. If you’re working on several thousands Vector3 in loops every frame, it can add up. If you have such functions show up high in the profiler, it’s worth to test how they perform by changing the math to be per component.
However, the cost of the function call depends on whether or not it will be inlined when converted into machine code. An inlined function has its body “copied” into the call site. This is done at the whims of the C# compiler (and C++ compiler for IL2CPP), but you can increase the odds of a function being inlined by using
[MethodImpl(MethodImplOptions.AggressiveInlining)], which causes the inlining to happen in the C# IL if possible.
(*) In the case of structs, only the constructors which take parameters: the default constructor cannot be overridden by the user and is not a function.
It is not a good advice, because it is the very last thing you should ever consider and gains are miniscule. Even mentioning this to a newbie nudges them towards premature optimization.
Also, disadvantages are severe. Do you know DRY? It goes against that. By manually inlining a call, you’re copy-pasting it, and if there is a bug, you’ll have to fix it in multiple places instead of just one.
They’re both methods, so it should be the same.
Be aware that some languages can eliminate function call overhead and inline function calls into code. C++ does that. C#, however, doesn’t.
Does unity have a garbage collector?
I think Unity uses the Boehm–Demers–Weiser garbage collector, a stop-the-world garbage collector. Whenever Unity needs to perform garbage collection, it stops running your program code and only resumes normal execution when the garbage collector has finished all its work. If am wrong so please let me know!
And you can tell the compiler to inline with the attribute:
[MethodImpl(MethodImplOptions.AggressiveInlining)]
I played around with it in the past, and tested the performance, with classes my static methods seem to have been generally inline, there was no difference, with structs, however, the same calls were slower, and only after I had added the attribute was it fast like the class version.
I used that in an explosion algorithm for a block-game, the explosion algorithm is exploring cells in a grid to compute the damages.
Just by inlining the Vector3 operations it has reduced the total execution time of the algorithm by about 20%.
Usually the gain isn’t that high (it is less than 2-3% in most cases), but in that case there were so many Vector3 operations that it really helped.
Of course, I profiled before doing that, and then I profiled again after.
I did that for other algorithms, and when the gain in under 3-5% I’ve reverted because the gain in performance wasn’t enough to justify the loss in maintainability.
I think that’s there’s no golden technical rule when optimizing, you always need to see the real impact of an optimization.
you profile to find out what part is too slow
then you try an optimization and profile to see if it has an impact
if it has a negligible impact you revert and you try another optimization
if it decrease the execution time enough, then you keep it, commit, and decide if you need another optimization or not
20% !!! I’m surprised it gave you so much performance boost.
I was expecting something in range of 5%-10% max.
Have you tried doing custom addition method instead of writing add by yourself ?
The cost of releasing goes up with every object/managed allocation you have though, even if you only release one so it’s not the only way to be thinking about this.
i’ve raised this issue in Unity.Mathematics here https://github.com/Unity-Technologies/Unity.Mathematics/issues/194 and i seem to have seen that they will do something like this for the default Vector3 structs as well in the new versions (maybe just for il2cpp, can’t seem to find the thread again)
the “in” parameter should only use with readonly structs, otherwise the compiler creates defensive copies, and the performance advantages are lost. But burst and IL2CPP can get around that.