Home / Blog / Basic code-based Unreal Engine optimization tricks

Basic code-based Unreal Engine optimization tricks

Modern games focus primarily on graphics yet tend to forget about CPU-related optimizations. While we are often GPU-bound, in certain situations, the CPU is the culprit!  

Unreal Engine is a great piece of tech, but if you look at the lifetime of an Actor object, you’ll see how many calls and operations are made not only at the start of the said object but also every frame. Moreover, most of the gameplay-related operations are done on the main thread. If we create a small game, the problem might be barely visible, but small inefficiencies become performance monsters when we scale up. 

Before you decide to do anything performance-wise, it’s essential to check whether we’re CPU- or GPU-bound. Either the graphics card or the processor is making us wait to finish the frame, which is why the whole game is working slower. The simplest way is to use the ‘stat unit’ command during gameplay or rely on Unreal Insights using command-line parameters ‘-trace=cpu,gpu,frame,bookmark’. In the latter, you can determine which exact functions work poorly. 

First and foremost: you don’t have to tick every component, every frame! Some do not need it at all! If you’re sure that updating every frame is not required, you can set the ‘bCanEverTick’ property to ‘false’. If you think you can limit the frequency of the tick, use ‘SetComponentTickInterval’ or ‘SetActorTickInterval’. You can find those parameters reflected in Blueprints as well. 

For more precise tick control, you can also use timers. Inside the Actor, it can look like this: ‘GetWorldTimeManager().SetTime(MyTimerHanlde, this, &MyActor::OnTimerTick, 2.0f, true);’ 

Another way of handling ticks and other Actor parameters at a scale is the Significance Manager, but this is a topic for a different post… 

There are cases where you have a lot of independent objects in a container. This is the perfect case for ParallelFor code: ‘ParallelFor(Array.Num(), [this](size_t Index) {/do sth/});’ This will automatically split operations within a block of code between threads. Remember to use Mutex to lock critical sections! 

Sometimes, you can stumble upon heavy tasks that won’t allocate new memory and are not expected to return results immediately. For those, you can use the AsyncTask call to delegate heavy lifting to a worker thread: ‘AsyncTask(ENamedThreads::AnyHiPriThreadNormalTask, {/do sth/});’ 

When you need more complex solutions, you might be interested in the FRunnable class, but we’ll talk about it some other time… 

To sum up: 

  • Before doing anything – profile! 
  • Turn off any tick function you don’t need (in Blueprints as well) 
  • You don’t have to tick every frame – change the tick rate or use the timer 
  • For independent operations, you should use ParallelFor for containers and Async Task for single, heavy operations 

Author

Grzegorz Wątroba

Principal Software Engineer

Game programmer since 2010, specializing in designing code architecture, optimization and porting on various platforms. Cooperated with Polish and foreign companies such as Bloober Team, One More Level, Fool’s Theory, Vile Monarch, Covenant.dev or Polygon Treehouse among others. Worked as well on the standard IT market for Nokia, Autodesk and Xara but games turned out to be his true love.

See more posts by this author

Looking for a proven work-for-hire team?

Get in touch to find out how we can support your game!

Hi! We use cookies and similar technologies to better know you and improve your experience with our website.
You can find out more by reading our Privacy Policy.