4
\$\begingroup\$

I'm an old coder, but a newbie to Godot. I'm using version 4.

I got some unexpected (to me) behavior when I tried this code:

var vectorA := Vector2(1.0,0.0)
var vectorB := vectorA
print("A ", vectorA, " B ", vectorB)
vectorA.x = 3
print("A ", vectorA, " B ", vectorB)

I would have thought that vectorB and vectorA were aliases: change one, and you change them both. But the output was as follows:

A (1, 0) B (1, 0)
A (3, 0) B (1, 0)

This implies that it allocated a totally new Vector2 object when I created vectorB. This brings up a lot of questions in my mind:

  • Can you make aliases of objects, or does it always make a brand new one?
  • Is it using pass-by-reference or pass-by-value when you call a function?
  • Do I need to worry about allocating tons of memory, whenever I set one object equal to another? Will this slow my game way down?
  • Or am I focusing on the wrong details, when I'm trying to optimize? I'm used to avoiding system calls and reusing old objects, but that may just be the wrong approach in Godot.

So what's going on behind the scenes when I set one Vector2 equal to another?

Thanks.

\$\endgroup\$

2 Answers 2

5
\$\begingroup\$

Checking the GDScript reference docs:

Built-in types

Built-in types are stack-allocated. They are passed as values. This means a copy is created on each assignment or when passing them as arguments to functions. The only exceptions are Arrays and Dictionaries, which are passed by reference so they are shared. (Packed arrays such as PackedByteArray are still passed as values.)

Because these are stack allocated, memory for the copy is practically free - it slots into the "temporary scratch pad" space that the containing function uses for its work. There's no garbage collection overhead or lifetime complications like there would be for heap allocated memory. Depending on how the code is compiled, it may even be possible for the copy to be elided entirely when the system can detect that it's unnecessary (I cannot speak to the details of what optimizations Godot performs or could be upgraded to perform in future).

That means that unlike in languages like Java, JavaScript, or ActionScript, you don't gain any benefit from declaring a single long-lived member variable and re-using it for function temporaries. In fact, doing so costs memory by increasing your heap allocation size. The situation here is more closely analogous to C# struct types, where temporaries and copies are cheap (as long as the structure is small).

There is a small overhead to copying the values, but for small data structures like Vector2 / Vector3 (just 8 and 12 bytes, respectively), this cost is negligible, and adding indirection to try to eliminate it may have a heavier performance impact of its own.

So, consider these small stack-allocated types safe to use as disposable temporaries, and don't unnecessarily complicate your code by trying to minimize them.

\$\endgroup\$
0
2
\$\begingroup\$

Vectors in Godot are value types, not reference types. Furthermore, they are not considered objects.

But since "object" might mean slightly different things depending on context, I'll be more specific... Vector2 is:

  • A value type: a type such that the memory layout of the variables is the value, and not a reference to the value.
  • A built-in type: a type that is provided as part of the language.
  • A composite type: a type that can be decomposed into simpler values (withing the constraints of the language).

Furthermore, Vector2 is not an entity type. So when you compare instances of Vactor2 you are checking if they have the same value not if they are the same.


You might be wondering is Vector2 a class? And the answer is that it depends on what you mean. Vector2 is a C++ class (internally there is a Vector2 class in the source code of Godot), but it is not a script class (it is not a class as far as the type system exposed by Godot is concerned).

Now, as you know, in general an "object" is an instance of a class. So if instances of Vector2 are objects also depends on what you mean.

In fact, there is an Object class, which is the root of the class hierarchy in Godot, so we will call "object" to any instance of a Object or a class that extends Object... And Vector2 is not a class (even if you consider it one, it does not extend Object). Which is why I claim that vectors in Godot are not considered objects.


Can you make aliases of objects, or does it always make a brand new one?

There are reference types in Godot, Vector2 is not one. You cannot make a direct reference to a Vector2. Yes, pointers are direct references. GDScript is a pointer free language.


Is it using pass-by-reference or pass-by-value when you call a function?

You pass by value. You cannot choose to pass by reference. And, again, Godot has both value types and reference types (so you can have a reference type passed by value).

You might work around this by passing the values inside of an Array (Array is a reference type, i.e. not a value type).


Do I need to worry about allocating tons of memory, whenever I set one object equal to another? Will this slow my game way down?

Depends on what you call "object"...

No, you don't have to worry about allocation when you are setting a variable to an instance of a reference type.

You might worry about some large composite value types, which would be the case of packed arrays (Packed*Array are value types).


Or am I focusing on the wrong details, when I'm trying to optimize? I'm used to avoiding system calls and reusing old objects, but that may just be the wrong approach in Godot.

I suppose you remember what they say about premature optimization. With that out of the way...

Yes, using a built-in method (I mean, something from the standard library) will be more performant than doing it in a script. However, I believe it is useful to figuring out if you can save a few calls, for another reason: a common experience for Godot beginners is to implement something to later discover Godot already has it.

And yes, a lot of allocations can become a problem. However, you will find that you can iterate your design much faster if you don't worry about it. And once you have settled on your design, you can work on optimize it if it is necessary.


I want to note that you have a time budget, which is split between your scripts, rendering, physics, and a few other systems. As long as they complete in time, the user would not perceive a performance degradation. Thus, if you are not going over budget, it is harmless if the GDScript code is less than optimal. But don't forget that the hardware is also a factor.


So what's going on behind the scenes when I set one Vector2 equal to another?

This is a copy:

var vectorA := Vector2(1.0,0.0)
var vectorB := vectorA

Simple. Being these value types, there is no ref-counting, no pointer fiddling, no flagging... It is a true, honest, copy.

Furthermore, these are local variables, correct? In that case they will be freed when the method ends. You are not leaking anything. You are not adding pressure to a garbage collector. Nothing like that.


A few extra notes about Godot type system and memory management:

  • All objects (instances of classes that are Object) are reference types.
  • If they extend RefCounted they are... reference counted.
  • Otherwise you free them manually.
  • If they extend Node you can use queue_free which will free them sometime in the future between frames.
  • Otherwise you would free them deterministically with free.
  • You could avoid a lot of Node allocations by working with server classes. But the code becomes more rigid. So, as I said above, iterate your design first, and afterwards see if you need this.

In fact, you will find that many game engines and frameworks - not only Godot - opt to have value type vectors (e.g. Vector2), plus they will have ways to keep lists of vectors compact in memory (e.g. PackedVector2Array). This makes it easier to move memory to the graphic back end, and this improvement in performance outweighs the losses caused by crating copies.


Finally, you could write C# or C++ for Godot which could potentially be more performant than GDScript (And I say potentially because bad code is bad regardless of language). Yet you can mix languages. So you can make your game in GDScript, and then port the parts that could use some extra performance.

\$\endgroup\$
0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .