Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add bunny mark benchmark #63

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 2 additions & 0 deletions benchmarks/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
addons
.import
12 changes: 12 additions & 0 deletions benchmarks/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
val godotProjects = listOf(
"bunnymark"
)

gradle.includedBuilds.forEach { build ->
if (godotProjects.contains(build.name)) {
tasks.register("runGodot-${build.name}") {
group = "godot"
dependsOn(build.task(":runGodot"))
}
}
}
120 changes: 120 additions & 0 deletions benchmarks/bunnymark/Benchmarker.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
extends Control

var fps_update_interval = 1.0
var elapsed_time = 0.0
var fps_label = null
var benchmark_container = null
var benchmark_node = null
var output_path = "user://benchmark_results.json"
var benchmark = null
var language = null
var arg_bench = "--bench="
var arg_lang = "--lang="

# bunnymark
var bunnymark_target = 60.0
var bunnymark_target_error = 0.5
var benchmark_is_bunnymark = false
var bunnymark_update_interval = 2.0
var stable_updates_required = 3
var bunnymark_update_elapsed_time = 0.0
var stable_updates = 0

var nativescript_languages = {
"kt": true
}

func _ready():
set_process(false)
fps_label = get_node("Panel/FPS")
benchmark_container = get_node("BenchmarkContainer")

benchmark = "BunnymarkV1DrawTexture"
language = "kt"

var args = OS.get_cmdline_args()
for arg in args:
if arg.substr(0, arg_bench.length()) == arg_bench:
benchmark = arg.split("=")[1]
elif arg.substr(0, arg_lang.length()) == arg_lang:
language = arg.split("=")[1]

start_benchmark(benchmark, language)

func _process(delta):
elapsed_time += delta
if elapsed_time >= fps_update_interval:
fps_label.text = "FPS: " + str(Engine.get_frames_per_second())
elapsed_time = 0.0
if benchmark_is_bunnymark:
update_bunnymark(delta)

func start_benchmark(benchmark_name, language):
var language_extension = language
if nativescript_languages.has(language) and nativescript_languages[language]:
language_extension = "gdns"
var script_path = "res://benchmarks/" + benchmark_name + "/" + language + "/" + benchmark_name + "." + language_extension
benchmark_is_bunnymark = benchmark_name.begins_with("Bunnymark")
bunnymark_update_elapsed_time = bunnymark_update_interval
var script = load(script_path)
benchmark_node = Node2D.new()
benchmark_node.set_script(script)
benchmark_node.add_user_signal("benchmark_finished", ["output"])
benchmark_node.connect("benchmark_finished", self, "benchmark_finished")
benchmark_container.add_child(benchmark_node)
if benchmark_node.has_method("add_bunny"):
set_process(true)
else:
benchmark_finished(0)

func benchmark_finished(output):
print("benchmark output: ", output)
benchmark_container.remove_child(benchmark_node)
write_result(output)
get_tree().quit()

func write_result(output):
print("written ", output)
var file = File.new()
file.open(output_path, File.READ)
var parse_result = JSON.parse(file.get_as_text())
var benchmark_file = null
if parse_result.get_error() == 0:
benchmark_file = parse_result.get_result()
if benchmark_file == null or typeof(benchmark_file) != TYPE_DICTIONARY:
benchmark_file = {
"benchmark_results": {}
}
file.close()
benchmark_file["benchmark_results"][benchmark + "_" + language] = output
var dir = Directory.new()
dir.remove(output_path)
file = File.new()
file.open(output_path, File.WRITE)
benchmark_file["run_date"] = OS.get_datetime()

func update_bunnymark(delta):
bunnymark_update_elapsed_time += delta
if bunnymark_update_elapsed_time > bunnymark_update_interval:
var fps = Engine.get_frames_per_second()
var difference = fps - bunnymark_target
var bunny_difference = 0
if difference > bunnymark_target_error:
bunny_difference = min(1000, max(1, floor(20 * difference)))
elif difference < -bunnymark_target_error:
bunny_difference = max(-1000, min(-1, -1*ceil(20 * difference)))
if difference < bunnymark_target_error:
stable_updates += 1
if stable_updates == stable_updates_required:
benchmark_node.finish()
else:
if bunny_difference > 0:
for i in range(bunny_difference):
benchmark_node.add_bunny()
else:
for i in range(-1*bunny_difference):
benchmark_node.remove_bunny()

stable_updates = 0

bunnymark_update_elapsed_time = 0.0
22 changes: 22 additions & 0 deletions benchmarks/bunnymark/Benchmarker.tscn
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[gd_scene load_steps=2 format=2]

[ext_resource path="res://Benchmarker.gd" type="Script" id=1]

[node name="Benchmarker" type="Control"]
anchor_right = 1.0
anchor_bottom = 1.0
script = ExtResource( 1 )

[node name="BenchmarkContainer" type="Node2D" parent="."]

[node name="Panel" type="Panel" parent="."]
margin_right = 83.0
margin_bottom = 14.0

[node name="FPS" type="Label" parent="Panel"]
anchor_right = 1.0
anchor_bottom = 1.0
text = "FPS:"
__meta__ = {
"_edit_use_anchors_": false
}
14 changes: 14 additions & 0 deletions benchmarks/bunnymark/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "godot3-bunnymark"
version = "0.1.0"
authors = ["Carter Anderson <mcanders1@gmail.com>"]
edition = "2018"

[lib]
path = "benchmarks/lib.rs"
crate-type = ["cdylib"]

[dependencies]
gdnative = { git = "https://github.com/GodotNativeTools/godot-rust" }
euclid = "*"
rand = "0.6"
21 changes: 21 additions & 0 deletions benchmarks/bunnymark/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2017 Michael "Carter" Anderson

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
93 changes: 93 additions & 0 deletions benchmarks/bunnymark/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
![Godot Bunnymark](images/banner.png)

Renders an increasing number of bunny sprites until a stable 60fps is hit. This is a decent test of real world usage as it combines Godot api usage with raw computation. I plan to update this README whenever significant performance changes occur or when new languages are added. Feel free to contribute language implementations or improvements!

## Disclaimer

The performance differences here might appear significant, but these benchmarks test the limits of each language (and the engine), which most games will never hit. Do not pick a language because it is "fastest" unless you know you need the fastest language. The smarter choice would be to pick the language you are most productive in. I personally think that most people will be more productive in GDScript or C#. A lot of time and energy went into making GDScript an integrated, seamless experience so it is a good starting point if you don't have a preference for any of the other languages listed.

It is also important to note that C#/Mono and GDNative are both very young. Its possible that their performance characteristics will change. And please don't use these benchmarks to say "Language X is better / faster than Language Y", we don't have enough data to make those assertions. If anything this proves that any of the choices below are viable. Choose the language that you are comfortable with and do your own testing to cover your own scenarios.

## Updates

Follow me on twitter [@cart_cart](https://twitter.com/cart_cart) if you want Bunnymark updates or updates on my other projects!

## Running

* Build C++ files
* Setup headers and bindings using [these directions](https://github.com/GodotNativeTools/godot-cpp)
* run ```make``` in the root of this project
* Build C# files
* run ```msbuild /p:Configuration=Tools;DebugSymbols=false;Optimize=true``` (some terminals might require escaping some of those symbols)
* Build Nim files
* Setup headers and bindings using [these directions](https://pragmagic.github.io/godot-nim/master/index.html)
* run ```nake build```
* Build D files
* `git clone` [godot-d](https://github.com/GodotNativeTools/godot-d) to your favorite directory
* [generate the bindings](https://github.com/GodotNativeTools/godot-d/blob/master/generator/README.md)
* run `dub add-local /path/to/godot-d/`
* run `dub build -b release`
* Build Rust files
* run `cargo build --release`
* Build Godot with the [ECMAScript module](https://github.com/Geequlim/ECMAScript) if you want to run the ECMAScript tests
* run ```sh run_benchmarks.sh```
* wait! This will take some time ... the automation code is still a bit naive so it takes awhile to converge on 60 fps
* view the results in ```USER_HOME_DIRECTORY/.godot/app_userdata/Bunnymark/benchmark_results.json```

## Benchmark Run - March 16, 2019

### BunnymarkV2

Attempts to draw as many sprites as possible using Sprite nodes. It calls GetChildren() to iterate over a list of Sprites and sets their positions. It also updates a Label's text once per frame. This test aims to be a better emulation of real world api usage than the V1 tests.

| Language | Bunnies Rendered |
|----------------------|------------------|
| ECMAScript/Javascript| 4660 |
| GDScript (Release) | 18560 |
| C#/Mono | 27555 |
| GDNative (D) | 28020 |
| GDNative (Nim) | 29920 |
| GDNative (C++) | 37480 |

### BunnymarkV1 - DrawTexture

Attempts to draw as many sprites to the screen as possible by drawing textures directly with VisualServer. This test focuses on compute / render performance and avoids making godot api calls.

| Language | Bunnies Rendered |
|----------------------|------------------|
| ECMAScript/Javascript| 4340 |
| GDScript (Release) | 20540 |
| C#/Mono | 36720 |
| GDNative (Nim) | 60056 |
| GDNative (C++) | 65580 |
| GDNative (D) | 67840 |

### BunnymarkV1 - Sprites

Attempts to draw as many sprites to the screen as possible by adding Sprite nodes. This test focuses on compute / render performance and avoids making godot api calls.

| Language | Bunnies Rendered |
|----------------------|------------------|
| ECMAScript/Javascript| 4300 |
| GDScript (Release) | 17639 |
| GDNative (Nim) | 37180 |
| C#/Mono | 37455 |
| GDNative (D) | 38740 |
| GDNative (C++) | 41935 |

### Hardware:

* CPU: Intel i7 7700k 4.2GHz
* GPU: Nvidia GeForce GTX 1070
* RAM: 16GB DDR4

### Build Info:
* OS: Arch Linux
* Official Godot 3.1 release

## Credits

* GDScript example adapted from: https://github.com/curly-brace/godot-bunnies. Thanks [@curly-brace](https://github.com/curly-brace)!
* [@Capital-EX](https://github.com/Capital-EX) provided the initial Nim tests, the D tests, and the display server tests
* [@endragor](https://github.com/endragor) updated the GDNative tests to work with Godot 3.0 stable
* [@Geequlim](https://github.com/Geequlim) added ECMAScript tests
6 changes: 6 additions & 0 deletions benchmarks/bunnymark/Test.tscn
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=2]

[ext_resource path="res://benchmarks/BunnymarkV1DrawTexture/d/BunnymarkV1DrawTexture.gdns" type="Script" id=1]

[node name="Test" type="Node2D"]
script = ExtResource( 1 )
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# BunnymarkV1DrawTexture

Attempts to draw as many sprites to the screen as possible by drawing textures directly with VisualServer. This test focuses on compute / render performance and avoids making godot api calls.
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
extends Node2D

var bunnies = []
var bunny_texture = load("res://images/godot_bunny.png")
var grav = 500
var screen_size

func _draw():
for bunny in bunnies:
draw_texture(bunny_texture, bunny[0])

func _process(delta):
screen_size = get_viewport_rect().size

for bunny in bunnies:
var pos = bunny[0]
var newPosition = bunny[1]

pos.x += newPosition.x * delta
pos.y += newPosition.y * delta

newPosition.y += grav * delta

if pos.x > screen_size.x:
newPosition.x *= -1
pos.x = screen_size.x

if pos.x < 0:
newPosition.x *= -1
pos.x = 0

if pos.y > screen_size.y:
pos.y = screen_size.y
if randf() > 0.5:
newPosition.y = -(randi() % 1100 + 50)
else:
newPosition.y *= -0.85

if pos.y < 0:
newPosition.y = 0
pos.y = 0

bunny[0] = pos
bunny[1] = newPosition
update()

func add_bunny():
bunnies.append([Vector2(screen_size.x / 2, screen_size.y / 2), Vector2(randi() % 200 + 50, randi() % 200 + 50)])

func remove_bunny():
if bunnies.size() == 0:
return

bunnies.pop_back()

func finish():
emit_signal("benchmark_finished", bunnies.size())
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[gd_resource type="NativeScript" load_steps=2 format=2]
[ext_resource path="res://src/godot/gdnlib/Benchmark.gdnlib" type="GDNativeLibrary" id=1]
[resource]
resource_name="BunnymarkV1DrawTexture"
class_name="BunnymarkV1DrawTexture"
library=ExtResource(1)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# BunnymarkV1DrawTexture

Attempts to draw as many sprites to the screen as possible by adding Sprite nodes. This test focuses on compute / render performance and avoids making godot api calls.
Loading