Performance implications of using native code instead of GDscript

Godot Version

4.4.1

Question

This is a more generic question, but I’ve read multiple times about ‘using native code’ when it comes to small, repetitive calculations as it implies better performance.

I personally use GDscript, so I wanted to ask:

By saying “native code”, does this refer to making a C# script in the .net version of Godot, or does it imply modifying the source code and building it directly to add the functionality?

Edit: For the record, I’ve already read up some of the docs, I just wanted potentially hands-on or experience-based opinions from anyone who may happen to read this with something to share.

I think it’s referring to this:

Oddly enough, this is an older docs page, the more recent version of which defines GDExtension

Agreed. So based on that, the answer is: It depends. Ultimately, I recommend you just make your code in GDScript, and then if you have to optimize, optimize. Because Godot optimizes a lot of things for GDScript and it gets better every version. C# does some things better. A GDExtension might do something better. But they both might not.

I have personally measured the performance of finding children using built-in methods from Godot’s internal C++, such as Node.find_children() and my custom GDScript implementations such as Helper.find_children_with_method() and Helper.find_children_with_signal(). Here are the results of that in a very complex scene tree full of a relatively deep structure, but probably nothing compared to a real, fully finished game:

func test_performance_of_find():
	var start_time = Time.get_ticks_usec()
	assert_not_null(test_scene.find_child("deepest four"), "Found deepest four.")
	var taken_time: Dictionary
	taken_time.find_child = Time.get_ticks_usec() - start_time
	gut.logger.info("find_child(\"deepest four\") took " + str(taken_time.find_child) + " usec.")
	
	start_time = Time.get_ticks_usec()
	assert_eq(test_scene.find_children("*", "ConfirmationDialog").size(), 1, "Found a ConfirmationDialog.")
	taken_time.find_children = Time.get_ticks_usec() - start_time
	gut.logger.info("find_children(\"*\", \"ConfirmationDialog\") took " + str(taken_time.find_children) + " usec.")
	
	start_time = Time.get_ticks_usec()
	assert_not_null(Helper.find_child_with_method(test_scene, "get_cancel_button"), "Found a ConfirmationDialog.")
	taken_time.find_child_with_method = Time.get_ticks_usec() - start_time
	gut.logger.info("find_child_with_method(test_scene, \"get_cancel_button\") took " + str(taken_time.find_child_with_method) + " usec.")
	
	start_time = Time.get_ticks_usec()
	assert_eq(Helper.find_children_with_method(test_scene, "get_cancel_button").size(), 1, "Found a ConfirmationDialog.")
	taken_time.find_children_with_method = Time.get_ticks_usec() - start_time
	gut.logger.info("find_children_with_method(test_scene, \"get_cancel_button\") took " + str(taken_time.find_children_with_method) + " usec.")

The C++ is definitely faster than what i could come up with, but my methods are only twice or 2.x times slower which, it is to be avoided in per-frame code, but i wouldn’t care if it was a rarely executed piece of logic. However the measurement does exist. I could provide more details if you wish and to know where i measure this, here is my Github project from which i take this stuff from: GitHub - tomsterBG/short: Godot short - Code shortening scripts for Godot 4.

So far my findings are, if you want to run something each frame, be very sparing with what it is that you’re running.