-
-
Notifications
You must be signed in to change notification settings - Fork 21.4k
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 String::replace_char(s)
methods for performance and convenience
#92475
base: master
Are you sure you want to change the base?
Add String::replace_char(s)
methods for performance and convenience
#92475
Conversation
String::replace_char(s)
methods for performanceString::replace_char(s)
methods for performance and convenience
989058a
to
d029832
Compare
611a214
to
6a49079
Compare
92cb3e7
to
4b78ad7
Compare
Uploaded an alternative approach based on the avove, any benchmarking would be appreciated! |
if (old_ptr[index] == p_key) { | ||
new_ptr[index] = p_with; | ||
} else { | ||
new_ptr[index] = old_ptr[index]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (old_ptr[index] == p_key) { | |
new_ptr[index] = p_with; | |
} else { | |
new_ptr[index] = old_ptr[index]; | |
const char32_t old_char = old_ptr[index]; | |
if (old_char == p_key) { | |
new_ptr[index] = p_with; | |
} else { | |
new_ptr[index] = old_char; |
Possible improvement to avoid accessing twice, but should be optimized out likely
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! I have previously shown in #100024 that a single-character replace can be up to 3x faster than string-replace, so I think this is warranted.
I have benchmarked this implementation, and it's a bit faster than the one I had (likely due to migrating the if changed
outside the loop, or because of the memcpy
):
String s = "Who is Frederic?Who is Frederic?Who is Frederic?";
auto t0 = std::chrono::high_resolution_clock::now();
for (int i = 0; i < 1000; i ++) {
String s1 = s.replace("e", "b");
}
auto t1 = std::chrono::high_resolution_clock::now();
std::cout << std::chrono::duration_cast<std::chrono::microseconds>(t1 - t0).count() << "us\n";
t0 = std::chrono::high_resolution_clock::now();
for (int i = 0; i < 1000; i ++) {
String s1 = s.replace_char('e', 'b');
}
t1 = std::chrono::high_resolution_clock::now();
std::cout << std::chrono::duration_cast<std::chrono::microseconds>(t1 - t0).count() << "us\n";
t0 = std::chrono::high_resolution_clock::now();
for (int i = 0; i < 1000; i ++) {
String s1 = s.replace("#", "b");
}
t1 = std::chrono::high_resolution_clock::now();
std::cout << std::chrono::duration_cast<std::chrono::microseconds>(t1 - t0).count() << "us\n";
t0 = std::chrono::high_resolution_clock::now();
for (int i = 0; i < 1000; i ++) {
String s1 = s.replace_char('#', 'b');
}
t1 = std::chrono::high_resolution_clock::now();
std::cout << std::chrono::duration_cast<std::chrono::microseconds>(t1 - t0).count() << "us\n";
Resulting in
249us
61us
24us
18us
I just have a few small comments, but otherwise I'm fully behind a merge!
Ah, I just about forgot: I think it would be good to check the string length for |
a58fadf
to
61e8fa5
Compare
Missed the earlier review but went over that, will push some changes in a few hours Using Will investigate using variations on |
6ad60f1
to
20b8fe5
Compare
Covered the above suggestions, but leaving the change to Improvements on that side can be part of a followup if desired, together with exposing this Edit: worked out the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Re-checked after the changes, looks even better now! I had approved earlier, and I'm still approving :)
if (p_keys.is_empty() || len == 0) { | ||
return *this; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about my earlier suggestion to add this here?
if (p_keys.size() == 1) {
return replace_char(p_keys[0], p_with;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point can test
Been busy but will push a cleaned up and improved version in the next few days Edit: Testing the new code and will push soon |
2c5cd08
to
ae5bc50
Compare
Will push parallel changes for |
@@ -429,6 +429,9 @@ class String { | |||
String replace_first(const char *p_key, const char *p_with) const; | |||
String replace(const String &p_key, const String &p_with) const; | |||
String replace(const char *p_key, const char *p_with) const; | |||
String replace_char(char32_t p_key, char32_t p_with) const; | |||
String replace_chars(const String &p_keys, char32_t p_with) const; | |||
String replace_chars(const char *p_keys, char32_t p_with) const; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can easily make a replace_chars(const Vector<char32_t> &, char32_t)
version as well, it can use the same method, but leaving with this for now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need i think, there's little chance somebody even owns vector<char32_t> instead of String.
ae5bc50
to
a3d7b48
Compare
a3d7b48
to
9195d5e
Compare
|
||
template <class T> | ||
static String _replace_chars_common(const String &p_this, const T *p_keys, int p_keys_len, char32_t p_with) { | ||
ERR_FAIL_COND_V_MSG(p_with == 0, p_this, "`with` must not be the NUL character."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will move this to the individual functions for easier use, but away from the computer right now
Will try do some performance comparison soon, but these replace in-place essentially, avoiding any COW or other manipulation, and also avoids creating any temporaries etc.
The multi-character one is optional but added for completeness for a few cases where multiple cases are replaced at once
Can expose to scripting if desired, but generally using characters is a bit more complicated in scripting so haven't added yetNow exposed, can drop if desired, but this should work well, the exposed methods aren't entirely useful in GDScript but are very useful in GDExtension I would say
Kept as separate commits for ease of editing until approval
See #92433 (comment)
A few more complex cases like
String::validate_filename
,OS::get_safe_dir_name
, andget_csharp_project_name
could be replaced using this, instead using a vector of characters, but for right now I keep this to the simple cases (mostly, seeAnimationLibrary::validate_library_name
for an exception)See also:
String::remove_char(s)
methods for performance and convenience #92476