- This test was repeated ~8 times and the results were captured once things started stabilizing around the same values, a better case would be to average things out over 1000, or even 10K samples.
- 32 was just a random number of NPCs that fit nicely on the map. Your game may have more or less actors that an ability may affect. Both are fine and supported. There is no hard limit to how many targets an Ability can affect.
- The CalculateDamageForActor method used by the Apply Damage Task just returned the base damage, there was no complex math going on (unfortunately).
- This is just looking at one part of an Ability, depending on what the rest of the Ability is doing it could still be an expensive Ability computationally.This was run on a Development version of the Editor on Windows, so in Release/Final version the numbers will be completely different (likely much faster due to removal of debug code and more aggressive compiler optimizations).
- As always, Profile your own game/platform as each game and platform has its own requirements and unique hardware.
There's a bit of overhead with using Async due to the setup, so our simple damage calculation is probably just on the cusp of being not worth it - but we still saved some time by farming things out to other cores so we could quickly move on to simply applying the damage and just asking the various cores for their result when we're ready for it. Pretty good considering this was done by simply checking a box on the Task!
So, What's the Down Side?
To understand that question we need to look at a couple ways Async work is completed within Unreal Engine 4 and, thus within, Able.
- The work is queued and guaranteed to be done by the next frame. That means you technically have to wait a frame to continue your work, depending on your needs this could be a deal breaker. This is how things like Async Collision Queries and other tasks that require a lot of work end up using this system.
- The work is farmed out immediately to the worker threads (assuming they aren't busy already), you don't have to wait a frame, but you incur a penalty if you attempt to grab work that isn't done yet (in which case it has to be done immediately). This is how things like CalculateDamageForActors works.
- If the work calls into Blueprints (e.g. CalculateDamageForActors, or IsValidForActor) it is incredibly important that those Blueprints not "set" any data on any Actors as you can run into some terribly annoying bugs. Reading data is fine (and ideally what those methods should be doing), but there is no way (currently) to ensure that behavior so your Scripters need to be aware and just keep that in mind.
Both versions require some overhead in wrapping up everything for the worker threads, so that's also something to be aware of.
Async is a powerful system that can improve performance, but that power requires a bit of care in handling.
Here are some helpful guidelines.
- Does your Ability need frame perfect collision (i.e. Simulators, Fighting Games)? If not, then turn on Async for any collision based tasks/features (Targeting, Queries, Sweeps, Raycasts, etc) and see if you can notice a change.
- Does your Ability affect a large number of Actors and/or have some complex damage calculations? Try turning on Async calculate and seeing if things improve.
- Abilities for NPCs should ideally make heavy use Async features as their performance isn't as noticeable/critical.
- Test and Profile! Don't be afraid to enable a feature, which is often just checking a box on a Task, try it out, and profile it if you want some solid numbers. Sometimes the numbers may tell you a different story than you would think naturally (due to any number of reasons).
So, we've taken a very broad look at what Async is and how it can help performance . However, Apply Damage isn't the only task in Able that allows for supports Async. Able as well as given some tips for its use. Finally, Able was written from the ground up to make use of Async whenever possible, both at the Ability and Task level of execution.
More tasks and further optimizations are being made all the time, so this list will continue to grow as Async isn't going away anytime soon and more and more engines are adopting it as a feature required in gameplay systems to reach the performance and scalability that is expected from their customers. If you still have questions, please check out our Tutorials/FAQ Page, or our Documentation.