Function object call vs calling member function
What is this?
With GDScript you can store functions as Callable objects into variables. It seems like there could be performance benefits to call function directly in variables than calling the member function of a object. This benchmark checks that.
Benchmark
Overview
This benchmark instantiates scene node objects in the amount of the property ‘length’. Benchmark_1 loops though each of those objects and calls their perform() method. Benchmark_2 loops through an array holding Callables to the same perform() methods and calls them.
Settings
Benchmark cycles: 100
Array length: 1000
There are 1000 objects and the benchmark_1 & _2 are repeated 100 times. The results shown in ms is the sum of running benchmark_1 and benchmark_2 those 100 times.
Results
The benchmark itself is run 10 times to find an average.
benchmark_1: 39.883ms, benchmark_2: 40.504ms, difference: -0.621ms
benchmark_1: 41.367ms, benchmark_2: 41.948ms, difference: -0.581ms
benchmark_1: 40.771ms, benchmark_2: 41.087ms, difference: -0.316ms
benchmark_1: 39.968ms, benchmark_2: 40.44ms, difference: -0.47199999999999ms
benchmark_1: 40.945ms, benchmark_2: 41.703ms, difference: -0.758ms
benchmark_1: 41.01ms, benchmark_2: 41.483ms, difference: -0.473ms
benchmark_1: 41.04ms, benchmark_2: 41.68ms, difference: -0.64ms
benchmark_1: 40.736ms, benchmark_2: 41.348ms, difference: -0.612ms
benchmark_1: 41.434ms, benchmark_2: 41.906ms, difference: -0.472ms
benchmark_1: 41.772ms, benchmark_2: 42.265ms, difference: -0.493ms
(sum of difference) / (amount of benchmark runs) = average.
-5.4ms / 10 = 0.54ms.
Conclusion
In this particular benchmark, calling the method through the object itself is faster than the same method stored in a variable, but not by a lot. Storing a function call as a variable and calling it like in benchmark_2 may have syntax benefits in some scenarios. In most examples it probably won’t make a noticeable difference performance wise.
Code
extends Node2D
@export var benchmark_cycles: int = 100
@export var length: int = 1_000
class Animal:
var identifier: String
var name: String
var favorite_word: String
var fear: int = 0
var happiness: int = 0
var age: int = 0
var tiredness: int = 0
var health: int = 0
var stamina: int = 0
var hunger: int = 0
func perform() -> bool:
return true
class Dog extends Animal:
var need_to_poo: int = 0
class EvilDog extends Dog:
var lust_for_human_blood: int = 0
var beings: Array[Animal]
var performs: Array[Callable]
var benchmark_1_results: Dictionary
var benchmark_2_results: Dictionary
var results: Dictionary
func _ready() -> void:
for i in length:
beings.append(EvilDog.new())
for being in beings:
performs.append(being.perform)
for i in benchmark_cycles:
benchmark_1_results[i] = benchmark_1()
benchmark_2_results[i] = benchmark_2()
measure_results()
func benchmark_1() -> float:
var time_start_benchmark: float = Time.get_ticks_usec()
for being: Animal in beings:
being.perform()
return Time.get_ticks_usec() - time_start_benchmark
func benchmark_2() -> float:
var time_start_benchmark: float = Time.get_ticks_usec()
for perform: Callable in performs:
perform.call()
return Time.get_ticks_usec() - time_start_benchmark
func measure_results() -> void:
var sum_1: float = 0.0
var sum_2: float = 0.0
for key in benchmark_1_results:
sum_1 += benchmark_1_results[key]
for key in benchmark_2_results:
sum_2 += benchmark_2_results[key]
sum_1 = sum_1 / 1000
sum_2 = sum_2 / 1000
print("benchmark_1: " + str(sum_1) + "ms")
print("benchmark_2: " + str(sum_2) + "ms")
print("difference: " + str(sum_1 - sum_2) + "ms")