Always use String as StringName backing internally.#104985
Always use String as StringName backing internally.#104985Repiteo merged 1 commit intogodotengine:masterfrom
String as StringName backing internally.#104985Conversation
6afd77a to
eeed782
Compare
Repiteo
left a comment
There was a problem hiding this comment.
Would this deprecate StaticCString by extension, or does that still fill a niche?
|
Yeah, |
Repiteo
left a comment
There was a problem hiding this comment.
I considered asking that to be added to this, but perhaps that's best suited as its own PR. It'd certainly warrant a separate commit in any case. But I'd be fine integrating this as-is; your call
Repiteo
left a comment
There was a problem hiding this comment.
On second thought, we probably should tackle the StaticCString removal here too. I checked back on the core meeting discussion points I missed, and it mentioned this PR/proposal needing more justification—I'd say removal of the StaticCString paradigm & consequential simplification of StringName creation makes for a solid justification. Plus, we'd be able to ensure that the performance/size savings still apply; I have no reason to believe they would change, but making sure now is the better call
eeed782 to
e689e1f
Compare
I didn't at first, because I was afraid it would be a far more complex change. Turns out I just needed to search-and-replace
I don't think it should affect persistent allocations, because Anyway, I tested allocations again just to be sure, and found this time, this PR decreased RAM usage by 3mb. I'm guessing at this point the difference might be general fluctuations, and it might be indicative that the change isn't large enough to measure. Edit: Here's a quick benchmark to show the difference in StringName s1 = "_ready";
StringName s2 = "Xready";
{
auto t0 = std::chrono::high_resolution_clock::now();
for (int i = 0; i < 100000000; i ++) {
// Test
String s = s1;
}
auto t1 = std::chrono::high_resolution_clock::now();
std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(t1 - t0).count() << "ms\n";
}
{
auto t0 = std::chrono::high_resolution_clock::now();
for (int i = 0; i < 100000000; i ++) {
// Test
String s = s2;
}
auto t1 = std::chrono::high_resolution_clock::now();
std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(t1 - t0).count() << "ms\n";
}Which prints on master: |
Repiteo
left a comment
There was a problem hiding this comment.
Wow, that was pretty painless after all. I thought it'd need a whole other commit, but it absolutely functions as one.
A proper merge can wait on the next core meeting to verify that other members had their concerns addressed, but I'm satisfied with the results 👍
Calinou
left a comment
There was a problem hiding this comment.
Tested locally (rebased on top of master 9f03bbf), it works as expected.
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) on the 3D platformer demo.
❯ hyperfine -iw1 -m25 "bin/godot.linuxbsd.template_release.x86_64.master --path /home/hugo/Documents/Git/godotengine/godot-demo-projects/3d/platformer --quit" "bin/godot.linuxbsd.template_release.x86_64 --path /home/hugo/Documents/Git/godotengine/godot-demo-projects/3d/platformer --quit"
Benchmark 1: bin/godot.linuxbsd.template_release.x86_64.master --path /home/hugo/Documents/Git/godotengine/godot-demo-projects/3d/platformer --quit
Time (mean ± σ): 1.739 s ± 0.033 s [User: 0.337 s, System: 0.372 s]
Range (min … max): 1.674 s … 1.796 s 25 runs
Benchmark 2: bin/godot.linuxbsd.template_release.x86_64 --path /home/hugo/Documents/Git/godotengine/godot-demo-projects/3d/platformer --quit
Time (mean ± σ): 1.690 s ± 0.075 s [User: 0.262 s, System: 0.361 s]
Range (min … max): 1.379 s … 1.787 s 25 runs
Summary
bin/godot.linuxbsd.template_release.x86_64 --path /home/hugo/Documents/Git/godotengine/godot-demo-projects/3d/platformer --quit ran
1.03 ± 0.05 times faster than bin/godot.linuxbsd.template_release.x86_64.master --path /home/hugo/Documents/Git/godotengine/godot-demo-projects/3d/platformer --quit
Stripped binary size:
master: 71,962,520 bytes- This PR: 70,377,432 bytes (-1.58 MB)
e689e1f to
91fe434
Compare
|
Thanks! |
Closes godotengine/godot-proposals#11517.
Benchmark
I benchmarked the launch of Godot of
mastervs this PR. There was a ~2% improvement (which is still within fluctuations, so no guarantees):Allocation Tracking
I tracked persistent allocations during a normal editor run (
/Users/lukas/dev/godot/godot/bin/godot.macos.editor.arm64 --path /Users/lukas/dev/godot/empty-game --editor), measured during the idle peak after launch, using Apple Instruments.1092408576b/1.02Gibon master1095411792b/1.02Gibon this prSo, the difference seems to be around
3mb/0.27%(additional cost in this PR vsmaster) in the editor.Binary size
The binary size shrinks by 300kb (0.16%), at least for me.
Verdict
I don't think the RAM saved is worth the costs of having a split
StringNamebacking.It's simpler, and more predicable, to know that any
StringNamecan convert toStringinstantly and without cost.Additionally, operations like
size()can be better inlined, making other operations generally faster. This is desirable especially becauseStringNameis supposed to be fast, and it can be detrimental if some operations are considerably slower thanString.This fact is exploitable to make additional RAM and performance savings, especially for well-known strings. For example, it could be tested whether
StringNamereferencing could be faster thanStringallocations (working towards interning). This would not be possible ifStringNamedoes not guarantee aStringbacking.