Change Node `set_name` to use StringName, slightly improves performance
This PR changes the Node::set_name() method to use StringName. This code skips StringName construction in the case where the desired name is already a valid node name. This improves performance by about 15% to 20% in my testing in the case where the desired name is already valid (the common case, the happy code path).
The code has been split up into multiple methods. invalid_node_name_index() finds the index of the first character that would be invalid for a node name. Then this is passed to validate_node_name_internal() which starts replacing the invalid characters from that index. The same logic used to exist combined into one method, but having it be separate allows us to speed up Node::set_name(). The logic in invalid_node_name_index() is even slightly simpler because it doesn't have to keep track of bool valid, it just returns if invalid. However, the existing validate_node_name() is still present as a wrapper, for being exposed to GDScript and is also used in many other parts of the codebase. I also renamed some local variables for readability. I'm open to any suggestions if this can be improved further.
Test and benchmarking code:
func _ready():
var nodes: Array = []
for i in range(1000000):
nodes.append(Node.new())
var start_time = Time.get_ticks_usec()
for i in range(1000000):
nodes[i].set_name(&"MyStringName")
var end_time = Time.get_ticks_usec()
print("Time elapsed: " + str(end_time - start_time))
Results:
Without calling set_name, just accessing the array:
Time elapsed: 28971 (varies between about 25k and 30k)
This PR with valid name, skips StringName construction:
Time elapsed: 132565 (varies between about 120k and 140k)
This PR with invalid name:
Time elapsed: 220087 (varies between about 200k and 230k)
Current master with valid name:
Time elapsed: 160325 (varies between about 150k and 170k)
Current master with invalid name:
Time elapsed: 213259 (varies between about 190k and 220k)
I also did some testing with longer StringNames and the speed improvement gets more significant as length increases.
- Bugsquad edit, closes: https://github.com/godotengine/godot/issues/96805
Since this breaks ABI compatibility, what can I do to preserve compatibility?
Since this breaks ABI compatibility, what can I do to preserve compatibility?
This should be addressable via bind_compatibility_method() (following the usual pattern with node.compat.inc, and _bind_compatibility_methods(), etc - take a look at one of the examples already in the engine, ie in TileMap)
And after that you should remove this line from the expected errors (it won't be needed once you add the compatibility method):
Validate extension JSON: Error: Hash changed for 'classes/Node/methods/set_name', from 04FD3184 to C4FB126E.
This doesn't break compatibility in C# because we don't expose the property accessors, so no additional changes should be needed for C#.
With this PR, we likely don't need to special-case this anymore in the C# bindings generator:
https://github.com/godotengine/godot/blob/6afd320984cf14198368cc6c53752813a02169e3/modules/mono/editor/bindings_generator.cpp#L1951-L1953
Needs rebase
Thanks!