Last active
December 2, 2020 15:47
-
-
Save xsellier/997409e2ce7058cf2354acdc4e332f45 to your computer and use it in GitHub Desktop.
Benchmark GDScript
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extends Node | |
export(bool) var click_to_run setget run | |
var outfile : File | |
var startusec : int = 0 | |
var elapsed : int = 0 | |
var testvar : int = 0 | |
var bigarray : Array = [] | |
func fileinit(): | |
outfile = File.new() | |
if outfile.open("res://README.md", File.WRITE) != 0: | |
print("ERROR OPENING FILE") | |
return 1 | |
for line in [ | |
"# GDScript Syntax Benchmarks", | |
"", | |
"Speed comparisons of various syntax alternatives within the GDScript language (Godot game engine). All code is within [benchmarks.gd](benchmarks.gd), including funcs referenced in results table.", | |
"", | |
"__SEE THE END OF THIS README FOR THE RESULTS TABLE__", | |
"", | |
"", | |
"## To Run Tests Yourself", | |
"", | |
"* Open the project in Godot", | |
"* Click the lone node in the Scene/Node panel", | |
"* Click the unchecked box in the Inspector for the exported variable 'Click To Run'", | |
"* Wait several seconds for the tests to run (Godot editor may appear frozen during this time)", | |
"", | |
"The script is a 'tool' and clicking this exported variable will trigger a setget function which actually runs the tests. The results will be printed in the standard output and written to your disk as README.md (clobbering the existing README.md)", | |
"", | |
"## Contributors", | |
"", | |
"Please only add [benchmarks.gd](benchmarks.gd) to your commits. This entire readme file is automaticaly generated by the script, so please do not commit changes to the readme itself. Thanks!", | |
"", | |
"## Results", | |
"", | |
"```Godot version: %s```" % Engine.get_version_info()["string"], | |
"", | |
]: | |
outfile.store_string("%s\n" % line) | |
return 0 | |
func printwrite(s): | |
print(s) | |
outfile.store_string("%s\n" % s) | |
func _ready(): | |
run(0) | |
func run(junk): | |
print() | |
if fileinit(): | |
print("ERROR OPENING FILE") | |
return 1 | |
bigarray = [] | |
bigarray.resize(1e6) | |
timeit("warmup", true) | |
compare_funcs_time("float_cast", "float_int") | |
compare_funcs_time("func_call", "func_callref") | |
compare_funcs_time("int_incr", "int_increq") | |
compare_funcs_time("dict_key", "dict_keystr") | |
compare_funcs_time("dict_key", "dict_keyvar") | |
compare_funcs_time("string_tempd", "string_temp") | |
compare_funcs_time("string_cast", "string_temp") | |
compare_funcs_time("dict_has", "dict_in") | |
compare_funcs_time("dict_itercast", "dict_iterkey") | |
compare_funcs_time("dict_size", "dict_len") | |
compare_funcs_time("array_append", "array_pushback") | |
compare_funcs_time("array_append", "array_index") | |
compare_funcs_time("array_len", "array_size") | |
compare_funcs_time("array_front", "array_izero") | |
compare_funcs_time("array_back", "array_ineg") | |
compare_funcs_time("var_script", "var_func") | |
compare_funcs_time("var_script", "var_self") | |
compare_funcs_time("iter_for_range", "iter_for_int") | |
compare_funcs_time("iter_for", "iter_while") | |
compare_funcs_time("matches", "ifs") | |
compare_funcs_time("parray_appendrw", "array_appendrw") | |
compare_funcs_time("dontcallfunc", "callfunc") | |
compare_funcs_time("inteval", "inteval_auto") | |
compare_funcs_time("arrayeval", "arrayeval_auto") | |
compare_funcs_time("dicteval", "dicteval_auto") | |
compare_funcs_time("nulleval", "nulleval_auto") | |
outfile.close() | |
func assign_startusec(): | |
startusec = OS.get_ticks_usec() | |
func assign_elapsed(): | |
elapsed = OS.get_ticks_usec() - startusec # do this first for accuracy | |
assert(startusec != 0) # then check that startusec had been properly assigned | |
startusec = 0 # reset startusec | |
func timeit(funcname, quiet=false): | |
assign_startusec() | |
call(funcname) | |
assign_elapsed() | |
if not quiet: | |
print("%s took %d usec" % [funcname, elapsed]) | |
func compare_funcs_time(funcname1, funcname2): | |
for fn in [funcname1, funcname2]: | |
assert(len(fn) <= 15) # for padding output strings | |
timeit(funcname1, true) | |
var elapsed1 = elapsed | |
timeit(funcname2, true) | |
var elapsed2 = elapsed | |
var e1_over_e2 = float(elapsed1)/float(elapsed2) | |
var fasterfunc | |
var slowerfunc | |
var fasterelapsed | |
var slowerelapsed | |
if elapsed1 > elapsed2: | |
fasterfunc = funcname2 | |
slowerfunc = funcname1 | |
fasterelapsed = elapsed2 | |
slowerelapsed = elapsed1 | |
else: | |
fasterfunc = funcname1 | |
slowerfunc = funcname2 | |
fasterelapsed = elapsed1 | |
slowerelapsed = elapsed2 | |
var result = "%15s *** %3.f%% faster than *** %-15s (%.3f vs %.3f sec)" % [fasterfunc, 100.0*(float(slowerelapsed)/float(fasterelapsed) -1.0), slowerfunc, fasterelapsed/1e6, slowerelapsed/1e6] | |
printwrite(" " + result) | |
func warmup(): | |
for i in 1e6: pass | |
func float_cast(): | |
for i in 1e6: | |
float(1) * 1.0 | |
func float_int(): | |
for i in 1e6: | |
1 * 1.0 | |
func func_call(): | |
for i in 1e6: | |
passonce() | |
func func_callref(): | |
for i in 1e6: | |
call('passonce') | |
func int_incr(): | |
var a = 0 | |
for i in 1e6: a = a + 1 | |
func int_increq(): | |
var a = 0 | |
for i in 1e6: a += 1 | |
func dict_key(): | |
var d = { "test": 0 } | |
for i in 1e6: | |
d.test | |
func dict_keystr(): | |
var d = { "test": 0 } | |
for i in 1e6: | |
d["test"] | |
func dict_keyvar(): | |
var d = { "test": 0 } | |
var key = "test" | |
for i in 1e6: | |
d[key] | |
func string_cast(): | |
for i in 1e6: | |
str(1) | |
func string_tempd(): | |
for i in 1e6: | |
'%d' % [1] | |
func string_temp(): | |
for i in 1e6: | |
'%s' % [1] | |
func dict_in(): | |
var a : Dictionary = { "test" : 0} | |
for i in 1e6: | |
if 'test' in a: | |
pass | |
func dict_has(): | |
var a : Dictionary = { "test" : 0} | |
for i in 1e6: | |
if a.has('test'): | |
pass | |
func dict_iterkey(): | |
var a : Dictionary = { "test" : 0} | |
for i in 1e6: | |
for s in a.keys(): | |
pass | |
func dict_itercast(): | |
var a : Dictionary = { "test" : 0} | |
for i in 1e6: | |
for s in a: | |
pass | |
func dict_size(): | |
var a : Dictionary = { "test" : 0} | |
for i in 1e6: a.size() | |
func dict_len(): | |
var a : Dictionary = { "test" : 0} | |
for i in 1e6: len(a) | |
func array_pushback(): | |
var a : Array = [] | |
for i in 1e6: a.push_back(0) | |
func array_append(): | |
var a : Array = [] | |
for i in 1e6: a.append(0) | |
func array_index(): | |
var a : Array = [] | |
a.resize(1e6) | |
for i in 1e6: a[i] = 0 | |
func array_len(): | |
for i in 1e6: len(bigarray) | |
func array_size(): | |
for i in 1e6: bigarray.size() | |
func array_front(): | |
for i in 1e6: bigarray.front() | |
func array_izero(): | |
for i in 1e6: bigarray[0] | |
func array_back(): | |
for i in 1e6: bigarray.back() | |
func array_ineg(): | |
for i in 1e6: bigarray[-1] | |
func var_script(): | |
for i in 1e6: | |
testvar # read | |
testvar = 0 # write | |
func var_func(): | |
var localvar : int = 0 | |
for i in 1e6: | |
localvar # read | |
localvar = 0 # write | |
func var_self(): | |
for i in 1e6: | |
self.testvar # read | |
self.testvar = 0 # write | |
func iter_for_range(): | |
for i in range(1e6): | |
for x in range(1): pass | |
func iter_for_int(): | |
for i in 1e6: | |
for x in 1: pass | |
func iter_for(): | |
for i in 1e6: pass | |
func iter_while(): | |
var i : int = 0 | |
while i < 1e6: i += 1 | |
func matches(): | |
var x : int = 0 | |
for i in 1e6: | |
match x: | |
1: pass | |
2: pass | |
_: pass | |
func ifs(): | |
var x : int = 0 | |
for i in 1e6: | |
if x == 1: pass | |
elif x == 2: pass | |
else: pass | |
func parray_appendrw(): | |
var pa : PoolIntArray = PoolIntArray([]) | |
var i : int = 0 | |
while i < 1e6: | |
pa.append(i) | |
pa[i] = pa[i] | |
i += 1 | |
func array_appendrw(): | |
var a : Array = [] | |
var i : int = 0 | |
while i < 1e6: | |
a.append(i) | |
a[i] = a[i] | |
i += 1 | |
func dontcallfunc(): | |
for i in 1e6: pass | |
func passonce(): pass | |
func callfunc(): | |
for i in 1e6: passonce() | |
func inteval(): | |
var x : int = 1 | |
for i in 1e6: | |
if x == 0: pass | |
func inteval_auto(): | |
var x : int = 0 | |
for i in 1e6: | |
if x: pass | |
func arrayeval(): | |
var a : Array = [1] | |
for i in 1e6: | |
if a == []: pass | |
func arrayeval_auto(): | |
var a : Array = [] | |
for i in 1e6: | |
if a: pass | |
func dicteval(): | |
var d : Dictionary = {0:0} | |
for i in 1e6: | |
if d == {}: pass | |
func dicteval_auto(): | |
var d : Dictionary = {} | |
for i in 1e6: | |
if d: pass | |
func nulleval(): | |
var x : Object = null | |
for i in 1e6: | |
if x != null: pass | |
func nulleval_auto(): | |
var x : Object = null | |
for i in 1e6: | |
if x: pass |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment