KEMBAR78
Change Node `set_name` to use StringName, slightly improves performance by aaronfranke · Pull Request #76560 · godotengine/godot · GitHub
Skip to content

Conversation

aaronfranke
Copy link
Member

@aaronfranke aaronfranke commented Apr 28, 2023

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.

@aaronfranke
Copy link
Member Author

Since this breaks ABI compatibility, what can I do to preserve compatibility?

@aaronfranke aaronfranke requested review from a team as code owners September 24, 2023 22:04
@dsnopek
Copy link
Contributor

dsnopek commented Sep 25, 2023

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.

Copy link
Member

@RedworkDE RedworkDE left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And Validate extension JSON: Error: Field 'classes/Node/methods/set_name/arguments/0': type changed value in new API, from "String" to "StringName". needs to be added to 4.1-stable.expected instead of the hash error that you removed.

@raulsntos
Copy link
Member

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:

// Special case for Node::set_name
bool whitelisted = getter->return_type.cname == name_cache.type_StringName &&
setter_first_arg.type.cname == name_cache.type_String;

@aaronfranke aaronfranke force-pushed the node-set-string-name branch 2 times, most recently from f272666 to 5129f1a Compare December 6, 2023 06:13
@aaronfranke aaronfranke requested a review from a team as a code owner December 6, 2023 06:13
@aaronfranke aaronfranke force-pushed the node-set-string-name branch 2 times, most recently from f8117cd to 5578944 Compare December 8, 2023 08:04
@aaronfranke aaronfranke force-pushed the node-set-string-name branch from 5578944 to 2dacd28 Compare January 6, 2024 08:28
@aaronfranke aaronfranke force-pushed the node-set-string-name branch from 2dacd28 to ed0f992 Compare January 6, 2024 10:51
@aaronfranke aaronfranke force-pushed the node-set-string-name branch 2 times, most recently from 79feb41 to 9b711b5 Compare January 31, 2024 06:07
@aaronfranke aaronfranke force-pushed the node-set-string-name branch from 3e3a3eb to 2d35330 Compare August 29, 2024 12:48
@aaronfranke aaronfranke force-pushed the node-set-string-name branch from 2d35330 to ffca5e8 Compare October 2, 2024 23:15
@aaronfranke aaronfranke force-pushed the node-set-string-name branch 2 times, most recently from 38dc65d to f727340 Compare December 18, 2024 07:58
@aaronfranke aaronfranke force-pushed the node-set-string-name branch from 8b0c1ae to 79ca6f4 Compare March 8, 2025 10:56
Copy link
Member

@Ivorforce Ivorforce left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR is very welcome! I had actually considered proposing this myself at some point, since it has very obvious performance improvement potential.
The slight slow down on invalid name calls is expected but acceptable as the cold path.

I think however your change can be made a bit simpler by focusing on the main proposal of changing the parameter, I don't think the change to validate_node_name is needed :)

Copy link
Member

@Ivorforce Ivorforce left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good code wise. I cannot vouch for the correctness of the compat header, but it looks correct too.

To re-iterate, I agree this is a good change. It's always good to match parameters closely to what is actually needed, and the tests show it's impactful.

I think there is one case where it breaks compatibility to GDScript code, which would be NodePath arguments:

set_name(^"Test")

However, I think this is acceptable.

@aaronfranke aaronfranke force-pushed the node-set-string-name branch from 79ca6f4 to 4c942d5 Compare March 27, 2025 22:51
@aaronfranke aaronfranke force-pushed the node-set-string-name branch 3 times, most recently from 0a02b62 to 8831bdc Compare April 10, 2025 08:30
@aaronfranke aaronfranke force-pushed the node-set-string-name branch from 8831bdc to a7cc734 Compare April 17, 2025 12:52
Copy link
Member

@Calinou Calinou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested locally, it works as expected. Code looks good to me.

Testing project: test_node_stringname_performance.zip
Same benchmark as in OP, but with 5 million iterations for each scenario instead of 1 million to reduce variance. (10 million will crash due to the node count being too high, even in master.)

Benchmark

PC specifications
  • CPU: Intel Core i9-13900K
  • GPU: NVIDIA GeForce RTX 4090
  • RAM: 64 GB (2×32 GB DDR5-5800 C30)
  • SSD: Solidigm P44 Pro 2 TB
  • OS: Linux (Fedora 41)

Using a release optimized export template (production=yes lto=full).

master

Time elapsed (valid name): 514795 usecs
Time elapsed (invalid name): 642047 usecs

This PR

Time elapsed (valid name): 458555 usecs
Time elapsed (invalid name): 654856 usecs

@Repiteo
Copy link
Contributor

Repiteo commented May 1, 2025

Needs rebase

@Repiteo Repiteo modified the milestones: 4.x, 4.5 May 1, 2025
@aaronfranke aaronfranke force-pushed the node-set-string-name branch from a7cc734 to a404b66 Compare May 1, 2025 22:22
@Repiteo Repiteo merged commit acf38b2 into godotengine:master May 2, 2025
20 checks passed
@Repiteo
Copy link
Contributor

Repiteo commented May 2, 2025

Thanks!

@aaronfranke aaronfranke deleted the node-set-string-name branch May 2, 2025 20:28
simpkins added a commit to simpkins/godot that referenced this pull request Jul 16, 2025
This fixes `Node::set_name()` to release the old unique name before
performing the rename.  godotengine#76560 changed the code to update `data.name`
before calling `_release_unique_name_in_owner()`, causing to incorrectly
try releasing the new name instead of the old name.

Fixes godotengine#108683
mlm-games pushed a commit to mlm-games/godot that referenced this pull request Jul 22, 2025
This fixes `Node::set_name()` to release the old unique name before
performing the rename.  godotengine#76560 changed the code to update `data.name`
before calling `_release_unique_name_in_owner()`, causing to incorrectly
try releasing the new name instead of the old name.

Fixes godotengine#108683
JoNax97 pushed a commit to Astor-Games/godot that referenced this pull request Oct 15, 2025
This fixes `Node::set_name()` to release the old unique name before
performing the rename.  godotengine#76560 changed the code to update `data.name`
before calling `_release_unique_name_in_owner()`, causing to incorrectly
try releasing the new name instead of the old name.

Fixes godotengine#108683
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Node::set_name and Node::get_name have inconsistent types

10 participants