Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add move semantics (constructor, assignment) to StringName. #100483

Merged
merged 1 commit into from
Dec 20, 2024

Conversation

Ivorforce
Copy link
Member

We've recently begun to introduce move semantics to core, for its performance benefits. Examples of functions that can benefit a lot from move semantics include SWAP (#100367), reduce_at and insert (#100477), and simple data transfer. For more information on motivation, see #100426.

@Ivorforce Ivorforce requested a review from a team as a code owner December 16, 2024 18:03
Copy link
Member

@hpvb hpvb left a comment

Choose a reason for hiding this comment

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

LGTM

@clayjohn
Copy link
Member

Now that we are getting into territory where the performance benefit is less obvious, i think we need to start profiling to show that this actually makes a measurable difference.

@Ivorforce
Copy link
Member Author

Ivorforce commented Dec 16, 2024

Now that we are getting into territory where the performance benefit is less obvious, i think we need to start profiling to show that this actually makes a measurable difference.

Generally I agree; not every type needs move semantics.
If you insist, I can run a benchmark on remove_at and insert as I did for #100477. But I'm fairly certain it's not needed; we should expect at least a ~2x improvement here too.

@clayjohn
Copy link
Member

Now that we are getting into territory where the performance benefit is less obvious, i think we need to start profiling to show that this actually makes a measurable difference.

Generally I agree; not every type needs move semantics. If you insist, I can run a benchmark on remove_at and insert as I did for #100477. But I'm fairly certain it's not needed; we should expect at least a ~2x improvement here too.

Profiling is not the same as benchmarking. Profiling means finding a place in Godot that triggers this case often and checking to see that you have saved time by making this change. Remember, the end goal for every performance PR is to make Godot games run faster. We need to actually show that this change will help games run faster.

@Ivorforce
Copy link
Member Author

Ivorforce commented Dec 17, 2024

Now that we are getting into territory where the performance benefit is less obvious, i think we need to start profiling to show that this actually makes a measurable difference.

Generally I agree; not every type needs move semantics. If you insist, I can run a benchmark on remove_at and insert as I did for #100477. But I'm fairly certain it's not needed; we should expect at least a ~2x improvement here too.

Profiling is not the same as benchmarking. Profiling means finding a place in Godot that triggers this case often and checking to see that you have saved time by making this change. Remember, the end goal for every performance PR is to make Godot games run faster. We need to actually show that this change will help games run faster.

Well, besides the 8500 uses of the StringName word across cpp and h files (vs about 22k for Variant, btw.), isn't potential for future acceleration reason enough to add move semantics? There are entire categories of acceleration that simply cannot be performed without these functions, such as move-semantics insert and remove_at mentioned above. Like, sure, maybe we don't need a move constructor for SpriteFramesEditor, but I think for the types as critical to the engine as StringName, we should do our best to make them as fast and idiomatic as possible, for benefits that span the entire codebase, as well as unlocking further improvements that simply cannot be done without them.

If you still think it's not warranted without a specific use case, I'm sure I can find something in the codebase that is actually, practically improved by this... But is it really worth it for 14 lines of pretty trivial code?

@Chaosus Chaosus added this to the 4.4 milestone Dec 17, 2024
Copy link
Contributor

@kiroxas kiroxas left a comment

Choose a reason for hiding this comment

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

This make sense for core data structures.

unref();
_data = p_name._data;
p_name._data = nullptr;
return *this;
Copy link
Contributor

Choose a reason for hiding this comment

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

if (_data != p_name._data) {
	unref();
	_data = p_name._data;
	p_name._data = nullptr;
}
return *this;

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe assign nullptr to p_name in both cases ? Or else the caller is still valid after the call, which is fine by move standard, but could be error prone.

Copy link
Member Author

@Ivorforce Ivorforce Dec 17, 2024

Choose a reason for hiding this comment

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

nullptr cannot be assigned to the caller because the if (_data != p_name._data) { test covers this == &p_name - so we could accidentally be setting our own pointer to nullptr accidentally. Besides, if we set its data to nullptr without taking ownership of it, we would need to unref it. move semantics aren't guaranteed to destruct the param anyway, but they are guaranteed to keep it in a valid state.
Regarding your code change suggestion, like last time I still prefer early exit to indented blocks :)

Copy link
Contributor

Choose a reason for hiding this comment

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

That's style anyway, so any will do, but you already have the indented block :). This just remove one line.

Copy link
Member Author

@Ivorforce Ivorforce Dec 17, 2024

Choose a reason for hiding this comment

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

Yeah - just to clear it up i prefer indented blocks to be the trivial path and unindented blocks to be the important path. This principle allows you to always know which is the important path, and it helps prevent double indentations or reformats if another early exit case pops up.

Copy link
Member

Choose a reason for hiding this comment

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

That's the general principle we go by, early exit is preferred

@clayjohn
Copy link
Member

Well, besides the 8500 uses of the StringName word across cpp and h files (vs about 22k for Variant, btw.), isn't potential for future acceleration reason enough to add move semantics? There are entire categories of acceleration that simply cannot be performed without these functions, such as move-semantics insert and remove_at mentioned above. Like, sure, maybe we don't need a move constructor for SpriteFramesEditor, but I think for the types as critical to the engine as StringName, we should do our best to make them as fast and idiomatic as possible, for benefits that span the entire codebase, as well as unlocking further improvements that simply cannot be done without them.

The road to bloat is paved with good intentions. potential for improvement is not the same thing as improvement. Idiomatic code isn't a benefit in itself. If a code change improves performance, then its not a burden to show that instead of just saying it.

Remember, the decision of the maintainers was that we shouldn't use move semantics widely since it makes the codebase harder to understand and more difficult to attract new contributors. Optimizing for maintainability is our number one priority. So if we are going to make something harder to understand and harder to maintain, we have to justify it.

Copy link
Member

@AThousandShips AThousandShips left a comment

Choose a reason for hiding this comment

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

LGTM

@clayjohn clayjohn self-requested a review December 17, 2024 17:13
@hpvb
Copy link
Member

hpvb commented Dec 17, 2024

The road to bloat is paved with good intentions. potential for improvement is not the same thing as improvement. Idiomatic code isn't a benefit in itself. If a code change improves performance, then its not a burden to show that instead of just saying it.

Remember, the decision of the maintainers was that we shouldn't use move semantics widely since it makes the codebase harder to understand and more difficult to attract new contributors. Optimizing for maintainability is our number one priority. So if we are going to make something harder to understand and harder to maintain, we have to justify it.

I agree with this 100%, however in this case given how very pervasive StringName is, and how often it is part of other structures, not putting move constructors and assignment operators in StringName will, in my mind, certainly prevent the compiler from being allowed to use moves at times where they would be very beneficial. I think we need them for String, StringName, our containers, core math types (using memcpy instead of 3 or 4 assignments), the use of unions makes these a weird one for the compiler probably, and variant.

I think once those are in place, adding more would indeed just be useless for performance unless a very clear (profile based!) case can be made for them.

@hpvb
Copy link
Member

hpvb commented Dec 18, 2024

@clayjohn does my comment address your concern? If so I'd like to see this merged so we can proceed with this sub project :)

@clayjohn
Copy link
Member

@clayjohn does my comment address your concern? If so I'd like to see this merged so we can proceed with this sub project :)

Yes :)

Additionally, we discussed this (and the other move semantics PRs) in a maintainer meeting today and agreed that we are happy to add move semantics to most core types as long as the implementation is relatively straightforward and doesn't require changes in how the type is used outside of core. This is a bit broader than our agreement from October.

@Repiteo Repiteo merged commit a94b559 into godotengine:master Dec 20, 2024
20 checks passed
@Repiteo
Copy link
Contributor

Repiteo commented Dec 20, 2024

Thanks!

@Ivorforce Ivorforce deleted the string-name-move-semantics branch December 20, 2024 08:43
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.

7 participants