godot-rust / gdext

Rust bindings for Godot 4
https://mastodon.gamedev.place/@GodotRust
Mozilla Public License 2.0
2.96k stars 182 forks source link

Performance optimizations #11

Closed Bromeon closed 1 month ago

Bromeon commented 1 year ago

There are some low-hanging fruits for making the library faster, this issue tracks such approaches.

Codegen

Runtime

lilizoey commented 1 year ago

caching StringNames in function calls can have a significant improvement. running these functions 1 million times (in a std::hint::black_box(..) call):

fn generate_string_name() {
    let foo = StringName::from("foo");
}

fn cached_string_name() {
    static CACHED: OnceLock<StringName> = OnceLock::new();
    let foo = CACHED.get_or_init(|| StringName::from("foo"));
}

takes on average 0.15 seconds for the first one and 0.0003 seconds for the second one in release mode.

we do make calls like StringName::from("name") a lot in our bindings like for method calls and such.

this does require implementing Send and Sync for StringName, but i think that should be safe. StringName is immutable and i believe thread-safe.

lilizoey commented 1 year ago

Manually writing an implementation of StringName::length where i cache: __method_name only, __call_fn only, and both, then running each function 1 million times with black_box in release mode, i get:

Uncached __method_name __call_fn both
Total Average Time 0.1979221145 0.04312633755 0.170186928 0.01706689335
Percent 100.00% 21.79% 85.99% 8.62%
Percent Diff 0.00% -78.21% -14.01% -91.38%
WinstonHartnett commented 1 year ago

Caching the method bind, builtin method, and utility function pointers with a static OnceLock produces the following speedups:

Function Kind Uncached Cached Percent
Node::get_child_count Method Bind 308ns 8ns 2.60%
Node::is_inside_tree Method Bind 212ns 5ns 2.36%
Node::is_node_ready Method Bind 209ns 5ns 2.39%
minf Utility 94ns 8ns 8.51%
floorf Utility 86ns 5ns 5.81%
StringName::length Builtin 86ns 7ns 8.14%
Bromeon commented 8 months ago

The cached function pointers have been implemented, and with them, StringNames also no longer need to be instantiated on each call.

For anyone interested in deeper insights, I wrote about it here: https://godot-rust.github.io/dev/ffi-optimizations-benchmarking

Bromeon commented 1 month ago

I think we can close this, several things have been optimized and new ones probably deserve their own issue.

Ughuuu commented 1 month ago

Damn, impressive. I am ready to give these changes a try. Thanks everyone!