godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
90.23k stars 21.04k forks source link

Slow access to getters, setters and node properties #9506

Closed Shin-NiL closed 5 months ago

Shin-NiL commented 7 years ago

Operating system or device - Godot version: Windows 10 Pro 64 - Godot 2.1.3-stable

Issue description:

Analyzing the bunnymark code once again, we found that the use of getters, setters and accessing nodes properties turn the execution of the code much slower.

this is the original bunny.gd code (full project here):

extends Sprite

var velocity = Vector2(0, 0)
var gravity = 3
var max_x = 640
var min_x = 0
var max_y = 480
var min_y = 0

func _ready():
    var tex = preload("res://wabbit_alpha.png")
    set_texture(tex)
    velocity.x = randf() * 10
    velocity.y = rand_range(5, 10)
    set_process(true)

func _process(delta):
    var pos = get_pos()

    pos.x += velocity.x
    pos.y += velocity.y
    velocity.y += gravity

    if (pos.x > max_x):
        velocity.x *= -1
        pos.x = max_x
    elif (pos.x < min_x):
        velocity.x *= -1
        pos.x = min_x

    if (pos.y > max_y):
        velocity.y *= -0.8
        pos.y = max_y
        if (randf() > 0.5):
            velocity.y -= randf() * 12;

    elif (pos.y < min_y):
        velocity.y = 0;
        pos.y = min_y;

set_pos(pos)

Exporting the executable to release mode we get: Bunnies: 7000 FPS: ~49

Changing it to avoid using Vector2 properties:

extends Sprite
var velocity_x = 0
var velocity_y = 0
var gravity = 3
var max_x = 640
var min_x = 0
var max_y = 480
var min_y = 0

func _ready():
    var tex = preload("res://wabbit_alpha.png")
    set_texture(tex)
    velocity_x= randf() * 10
    velocity_y = rand_range(5, 10)
    set_process(true)

func _process(delta):
    var pos = get_pos()
    var pos_x = pos.x
    var pos_y = pos.y
    pos_x += velocity_x
    pos_y += velocity_y
    velocity_y += gravity
    if (pos_x > max_x):
        velocity_x*= -1
        pos_x = max_x
    elif (pos_x < min_x):
        velocity_x*= -1
        pos_x = min_x
    if (pos_y > max_y):
        velocity_y *= -0.8
        pos_y = max_y
        if (randf() > 0.5):
            velocity_y -= randf() * 12;
    elif (pos_y < min_y):
        velocity_y = 0;
        pos_y = min_y;
    set_pos(Vector2(pos_x, pos_y))

Exporting the executable to release mode we get: Bunnies: 8000 FPS: ~54 (If we were able to get rid of get_pos and set_pos the results would be even better)

My question is: is that behavior expected? The user _807_ from godotdevelopers forum even adapted the code to use the shower of bullets approach with no notable gain of performance. Here's the full discussion thread.

reduz commented 7 years ago

indexing is supposed to be slower, not that it couldn't be optimized a bit eventually. it could be interesting to test using GDNative C++ and C# in comparison in Godot 3.0

On Wed, Jul 5, 2017 at 8:22 AM, Shin-NiL notifications@github.com wrote:

Operating system or device - Godot version: Windows 10 Pro 64 - Godot 2.1.3-stable

Issue description:

Analyzing the bunnymark code once again, we found that the use of getters, setters and accessing nodes properties turn the execution of the code much slower.

this is the original bunny.gd code (full project here https://github.com/Shin-NiL/Godot-BunnyMark-CPP/tree/master/projects/godot-bunnymark-gdscript ):

extends Sprite

var velocity = Vector2(0, 0) var gravity = 3 var max_x = 640 var min_x = 0 var max_y = 480 var min_y = 0

func _ready(): var tex = preload("res://wabbit_alpha.png") set_texture(tex) velocity.x = randf() * 10 velocity.y = rand_range(5, 10) set_process(true)

func _process(delta): var pos = get_pos()

pos.x += velocity.x pos.y += velocity.y velocity.y += gravity

if (pos.x > max_x): velocity.x = -1 pos.x = max_x elif (pos.x < min_x): velocity.x = -1 pos.x = min_x

if (pos.y > max_y): velocity.y = -0.8 pos.y = max_y if (randf() > 0.5): velocity.y -= randf() 12;

elif (pos.y < min_y): velocity.y = 0; pos.y = min_y;

set_pos(pos)

Exporting the executable to release mode we get: Bunnies: 7000 FPS: ~49

Changing it to avoid using Vector2 properties:

extends Sprite var velocity_x = 0 var velocity_y = 0 var gravity = 3 var max_x = 640 var min_x = 0 var max_y = 480 var min_y = 0

func _ready(): var tex = preload("res://wabbit_alpha.png") set_texture(tex) velocity_x= randf() * 10 velocity_y = rand_range(5, 10) set_process(true)

func _process(delta): var pos = get_pos() var pos_x = pos.x var pos_y = pos.y pos_x += velocity_x pos_y += velocity_y velocity_y += gravity if (pos_x > max_x): velocity_x= -1 pos_x = max_x elif (pos_x < min_x): velocity_x= -1 pos_x = min_x if (pos_y > max_y): velocity_y = -0.8 pos_y = max_y if (randf() > 0.5): velocity_y -= randf() 12; elif (pos_y < min_y): velocity_y = 0; pos_y = min_y; set_pos(Vector2(pos_x, pos_y))

Exporting the executable to release mode we get: Bunnies: 8000 FPS: ~54 (If we were able to get rid of get_pos and set_pos the results would be even better)

My question is: is that behavior expected? The user 807 from godotdevelopers forum even adapted the code to use the shower of bullets approach with no notable gain of performance. Here's the full discussion thread. https://godotdevelopers.org/forum/discussion/18435/bunny-mark-performance

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/godotengine/godot/issues/9506, or mute the thread https://github.com/notifications/unsubscribe-auth/AF-Z25Mv_N2OIKN7ig8D2pO0DamJPrnqks5sK3IBgaJpZM4OOM70 .

Zylann commented 7 years ago

I noticed too that access to Vector2 and Vector3 properties was really slowing down operations in my own GDScript benchmark batch, in general using vector math as much as possible is faster (even if it's only about updating one of the components, it's an interpreted language remember, slow things aren't always what we think^^).

Shin-NiL commented 7 years ago

@reduz what I did was change my C++ Bunnymark module (Godot 2.1.3) to not access the Vector2 properties (as I did with the GDScript version). But I didn't see any performance gain doing that.

@Zylann thanks, nice tip :)

Calinou commented 4 years ago

@Shin-NiL Can you still reproduce this in Godot 3.2.1 or the master branch?

Shin-NiL commented 4 years ago

@Calinou Here's the results running on Windows 10 Pro 64 (new machine), Godot 3.2.1-stable, release mode:

Vector2 version Bunnies: 12000 FPS: ~55

Non Vector2 version Bunnies: 17000 FPS: ~55

akien-mga commented 4 years ago

@Shin-NiL Do you have an updated test project for 3.2.x to check those Vector2 and non Vector2 variants?

Shin-NiL commented 4 years ago

@akien-mga I did a new test with 3.2.2-stable, running on the same machine I got the same results as before with 3.1.1-stable.

akien-mga commented 4 years ago

What I meant is: can we have the test project?

It's useful to have benchmark results, but it's better to have access to the benchmark to actually debug what makes things slower :)

Shin-NiL commented 4 years ago

@akien-mga ok, I got it :P

It's not beautiful, but I think it works.

nonvector_bunny_mark.zip

Calinou commented 2 years ago

Could someone check whether the performance gap has changed in 4.0alpha?

and-rad commented 1 year ago

Here are my findings, 4.0beta:

Vector2: 17.000 @ 44fps Non Vector2: 17.000 @ 33fps

If I turn gravity into a vector and change the code in the Vector2 version from

position.x += velocity.x
position.y += velocity.y
velocity.y += gravity

to

position += velocity
velocity += gravity

the Vector2 version increases to run at 41 fps.

So it seems like accessing an object's individual properties is the major cause of the slowdown, which I think is an expected outcome? That's just the nature of interpreted languages like GDScript.

Calinou commented 1 year ago

So it seems like accessing an object's individual properties is the major cause of the slowdown, which I think is an expected outcome? That's just the nature of interpreted languages like GDScript.

Couldn't this be sped up for types where property names are known in advance, especially built-in Variant types other than Object?

KoBeWi commented 5 months ago

Tested in 4.3 with typed GDScript and the issue is resolved.

Shin-NiL commented 5 months ago

Thank you!