Poor performance

imaethan

New member
So I've been testing performance recently and noticed in the profiler that when there's a decent number of enemies enabled the performance tanks, due to BehaviorManager.Update(), it's 30 - 40% of Update.ScriptRunBehaviourUpdate.

Any tips on optimising my behaviour trees? I've attached a screenshot below.

opsive01.jpg

I've also attached a screenshot of the profiler, before and after I disable the enemies.

opsive02.jpg

Any help or suggestions would be much appreciated.
 
You need to show more of the profiler. How much ms per frame does it take?
If you use deep profiling you'll see which tasks are problematic. Post a breakdown of the call stack

I've done extensive testing and profiling on behavior designer performance vs other alternatives (such as node canvas) and behavior designer performs the best at runtime. Here is the basic overhead I benchmarked for running through a tree with simple sequence of tasks with minimal processing (waiting, moving to random location, look at random location).

1661659620777.png

As you can see unless you have over 100 actors, the trees should be negligible to your frame budget.
These were profiled on mobile, for pc you may have better results.
 
You need to show more of the profiler. How much ms per frame does it take?
If you use deep profiling you'll see which tasks are problematic. Post a breakdown of the call stack

I've done extensive testing and profiling on behavior designer performance vs other alternatives (such as node canvas) and behavior designer performs the best at runtime. Here is the basic overhead I benchmarked for running through a tree with simple sequence of tasks with minimal processing (waiting, moving to random location, look at random location).

View attachment 9491

As you can see unless you have over 100 actors, the trees should be negligible to your frame budget.
These were profiled on mobile, for pc you may have better results.
This is with 50 enemies enabled.

opsive03.jpg

From looking at the screenshot of the tree itself, does it look to be setup correctly?
 
If you expand it further you'll be able to see which task is using up the most time. Yours seems high for 50 enemies. Also if it is creating 45kb GC per frame it might be worth investigating.

1661698603440.png

As you can see in mine there is

1 Shoot Task
1 Movement Task
2 Follow Task

--

As a general suggestion you should try to write your tasks to create 0 garbage at runtime. If you need to instantiate things do it in in the Awake() method of the task so that it can be re-used each time (or pool it from an object pool).

Another thing is I notice from your first screenshot of your tree, you use a lot of conditional nodes. If you always have to do the same thing, it's more performant to combine it into your own task.

For example my FollowTask will also check the distance to the player, line of sight, etc.

When you have just a few nodes or actors it doesn't really matter, but if you wish to optimize the performance as much as possible, reducing the # of nodes will help.
 
Do you happen to have any screenshots of your behaviour tree so I can get a better idea of where I'm going wrong? I'm just using tasks from the movement pack so they're not my own.
Also I don't see individual tasks under the BehaviorManager.Update in the profiler.

opsive04.jpg

So for example I'm best off making a copy of the patrol task script and adding the compare field value stuff into that?
 
To see all the individual calls you need to enable "deep profile" option in the profiler window. It's one of the buttons on the top bar of the profiler

1661704538042.png

Looks like there are 1921 instances of memory allocation in your update (using 0.07ms) but that doesn't add up to the remaining 2.2ms~
Once you look into the deep profile you should be able to see what is taking the bulk of the 2.2~ ms

My enemy behaviors are probably not as complex as yours but here is an example:

If the player is nearby, it will move away and then try to shoot twice towards the player.
The main thing is I combine the conditionals into tasks whenever it's repetitive.
For example, every enemy that wants "move away" behavior would probably want to check distance to player. So instead of having to define a couple nodes to make the same behavior in the each tree, I just combine the code so it's in 1 node.

This improves performance but also makes the tree easier to read

1661704440707.png
 
Just by looking at your screenshot it looks like you are using a lot of reflection tasks and reflection is notoriously slow. I recommend replacing the reflection tasks with the C# equivalent and your performance will likely improve a lot.
 
Just by looking at your screenshot it looks like you are using a lot of reflection tasks and reflection is notoriously slow. I recommend replacing the reflection tasks with the C# equivalent and your performance will likely improve a lot.
So I'd be better off putting the compare field values/bools and stuff into their own task script or? Confused by "C# equivalent" here. Apologies for me being dumb, I'm quite new to behaviour trees.
 
Last edited:
So I'd be better off putting the compare field values/bools and stuff into their own task script or?
Yes, it's more efficient to write a native c# task. This page provide a good comparison for reflection speeds:

 
Yes, it's more efficient to write a native c# task. This page provide a good comparison for reflection speeds:

Is it possible that you have an example of a C# equivalent of the reflection task? Or can point me to something in the documentation?
 
Any task that has the magnifying glass (and is in the Reflection) category is a reflection task. For example instead of calling GetField you should access the component field directly, myComponent.field.
 
Any task that has the magnifying glass (and is in the Reflection) category is a reflection task. For example instead of calling GetField you should access the component field directly, myComponent.field.
So I've used SetVariableValue in my enemy script instead of "Get Field" in the behaviour tree.

opsive06.jpg

Instead of comparing the field value I'm now setting the shared variable for Alive in my enemy script then using Compare Shared Bool. Is this a better way?

opsive07.jpg

Yeah, that's done it. Replaced all the compare field values with compare shared bools.

1661768343814.png
 
Last edited:
Glad you are already seeing the effects of replacing those tasks. Replacing all of the magnifying tasks with the C# equivalent should give you even better results and have no allocations per frame.
 
Glad you are already seeing the effects of replacing those tasks. Replacing all of the magnifying tasks with the C# equivalent should give you even better results and have no allocations per frame.
How would I check the value of a Shared Variable bool in my C# enemy script? GetValue()?

I'm looking at the Accessing Variables documentation but it only has information on setting values.
 
Last edited:
No, you'd use the Value property. You can both set and compare with that property.
 
If you are within the task you can assign the value within the editor, similar to the image on this page:


From there you can do:

Code:
attacking.Value = false;
if (!attacking.Value)

This is all within your own task. If you are outside the task you can use GetVariable and cast the results.

Code:
var attacking = (SharedBool)bhTree.GetVariable("Attacking")
 
Top