Testing and Profiling some of the many different get all actor methods in Unreal Engine 5 with C++

There are many ways to get all actors in Unreal, they're all great, but going off the recent myth-debunk video I wanted to have some fun testing them out.

Unreal Engine 5 20000 cubes in the open world template level testing get all actor functions
Testing the functions with 20,000 cubes

Software Versions: Unreal Engine 5.5.1 | Rider 2024.3.2

Project Name: MyProject

The Unreal Engine YouTube account recently posted a video titled "Myth-Busting Best Practices in Unreal Engine | Unreal Fest 2024" and it's a wonderful video that I highly recommend. Mentioned in the video is that GetAllActorsOfClass is faster than GetAllActorsWithInterface and GetAllActorsWithTag so I wanted to see if I could test it in Unreal. I'm not great at testing or profiling so I took it as an opportunity to practice those skills. Below is the video, the discussion around GetAllActorsOfClass starts around 36 minute mark.

YouTube Video: Myth-Busting “Best Practices” in Unreal Engine | Unreal Fest 2024

Take all results with cation and always profile for your own specific project. As the video mentions throughout, profiling is the best way to understand what works best for you and your game. This post is really only for practice and fun, all the functions that I'll talk about are all great and for my games they're all very performant. Use whatever works best for you and your project.

Always profile for you project. Results may vary.

So is GetAllActorsOfClass faster than GetAllActorsWithInterface, and in my rudimentary testing, yep, absolutely. In all my testing GetAllActorsOfClass typically reigned supreme over all get all methods. Results may vary depending on the circumstance, but GetAllActorsOfClass or using TActorRange in a loop where the functions that were usually the fastest.

I took this testing opportunity to try out QUICK_SCOPE_CYCLE_COUNTER and SCOPE_SECONDS_COUNTER, two super great functions we can use for testing. Refer to Epic's official stat overview documentation for complete details, but breifly QUICK_SCOPE will return the time in the Unreal Insights profiler and SCOPE_SECONDS will set a double/float variable for logging, while both being scoped to their respective sections.

For testing I created an actor that spawned thousands of actors on BeginPlay and then each second ran a different technique of getting all actors. The only actors spawned were very simple TestActor and TestActorInt classes that I crated for this test. The only difference is that TestActorInt inherits an interface.

void AMyGetAllActors::TestGetAll()
{
	QUICK_SCOPE_CYCLE_COUNTER(MyStat_GetAllActorsOfClass);

	double ThisTime = 0;
	TArray<AActor*> FoundActors;
	if (MyWorld != nullptr)
	{
		SCOPE_SECONDS_COUNTER(ThisTime);
		
		UGameplayStatics::GetAllActorsOfClass(MyWorld, AActor::StaticClass(), FoundActors);
	}

	UE_LOG(LogTemp, Log, TEXT("Time: %.8f, TotalActors: %d"), ThisTime, FoundActors.Num());
}

By all accounts my testing isn't perfect, but I used QUICK_SCOPE_CYCLE_COUNTER at the beginning of the function to profile the function in insights and then added SCOPE_SECONDS_COUNTER to set the time right before the GetAll function for logging.

For a visual representation I created list view widget that listened for a delegate broadcast with a sorted TArray derived from a TMap to provide all the values. In most situations GetAllActorsOfClass was always faster than GetAllActorsWithInterface and GetAllActorsWithTag.

Unreal Engine 5 1000 cubes in the open world template level testing get all actor functions
Testing the functions with 1,000 actors spawned
Unreal Engine 5 20000 cubes in the open world template level testing get all actor functions
Testing the functions with 20,000 actors spawned

A lot of my testing happened inside the editor, but the results were similar in a packaged build. The results are sorted by time every second so the rows will occasionally change places as the test runs.

To enable Unreal Insights click the trace button at the bottom of the editor, enable Stat Named Events for QUICK_SCOPE results and then select the Unreal Insights (Session Browser) option.

Unreal Engine 5 editor highlighting the trace menu options to enable stat events and Unreal Insights
Unreal Insights Session Browser

With the session browser open you start and stop recordings using the circular graph button next to Trace menu highlighted in the image below.

Unreal Engine 5 session browser unreal insights recording
Unreal Insights recording session

Once done you can double click to open the results. The quick scoped results aligned with the scoped seconds counter results, GetAllActorsOfClass was usually much faster than GetAllActorsWithInterface and GetAllActorsWithTag.

Unreal Engine 5 profiling unreal insights get all actor methods MyStat_GetAllActorsOfClass method stat highlighted
MyStat_GetAllActorsOfClass
Unreal Engine 5 profiling unreal insights get all actor methods MyStat_GetAllActorsWithInterface method stat highlighted
MyStat_GetAllActorsWitInterface

The Unreal Myth Busting video is a lot of fun with a lot of great information. The video prompted a good opportunity to start getting more comfortable with testing and profiling.

If you cloned the GD Tactics Repo you can run the my example by directly dragging in the BP_Add_GetALL_Widget and MyGetAllActors actors into the OpenWorld level. You can adjust the amount of actors spawned via the details section for the MyGetAllActors actor.

I defer to Epic's Unreal Engine developers or other C++ UE experts for more factual results. A lot of times it might not be the function that's the problem, but rather how the function is being used that might be effecting performance. Each project is different, each game has specific use cases, so testing and profiling may differ per project.

For my example I had an isolated open world with six timers running every second, so my example might not be the best for another project. However, I was happy to confirm what the video mentioned, that yes indeed, GetAllActorsOfClass is faster than GetAllActorsWithInterface and GetAllActorsWithTag.

All the GetAll actor functions are great that I'll likely use throughout development without affecting performance, however, I'll likely always keep performance in mind never trying to overuse something or use one of the functions in Tick.

Loading...