Inside UE Source: FUObjectHashTables - the magic behind GetAllActors* Nodes
I was curious about the performance implications of the GetAllActorsFromClass blueprint node vs GetAllActorsWithInterface and GetAllActorsWithTag.
Do all of these functions require traversing all objects in the world? They all have a note in BP "This is a slow operation. Use with caution.e.g do not use every frame"
So I put on some cool jazz and started code spelunking...
After a few code searches and debugging with some strategic breakpoints, I surmised the following:
When a new UObject is created, UObjectBase:: AddObject gets called to "Add a newly created object to the name hash tables and the object array".
This results in a call to HashObject(), which is a global function and stores the object in a class specific hashmap.
FUObjectHashTables is the global singleton that manages these HashMaps for every Class that had any UObjects instantiated.
As far as the GetAllActorsWith* nodes, they all utilize a TActorIterator to fetch objects.
Now the base class TActorIteratorBase is a templated class. So a new class is created for each needed object class type.
The GetAllActorsFromClass Node uses a TActorIterator and ultimately ends up passing the specified class and through to GetObjectsOfClassThreadSafe, which is the workhorse that loops through the class-specific hashmaps stored in the FUObjectHashTables singleton.
Ultimately, the results from GetObjectsOfClassThreadSafe end up getting stored in FActorIteratorState, which is used for the iteration.
But here is the deal - since GetAllActorsFromClass is only interested in a single target class, it only needs to traverse the smaller optimized class-specific hash. (Or at least, only in the HashMaps for the classes derived from the target. It may be less than optimal for super deep hierarchies with lots of objects)
The other two nodes end up calling GetObjectsOfClass with the Actor class, which adds all the derived classes from Actor to a TSet, and then uses that in the call to GetObjectsOfClassThreadSafe. This ends up searching all of the HashMaps for all the classes derived from Object. (180+ hashmaps in my basically empty demo project).
In summation, the engine maintains a HashMap for each class of all the instantiated objects of that class. These HashMaps are used by GetAllActor* nodes to iterate over these objects. Any new object created is added to the appropriate map.
GetAllActorsFromClass should be orders of magnitude faster than GetAllActorsWithInterface and GetAllActorsWithTag, since it can utilize a specific HashMap. Furthermore, IMHO, it should be fast enough to use every frame. Especially if you have a manageable amount of the the target class.