TSharedPtr vs. UOBJECT(), pros and cons?

304 views Asked by At

In Unreal Engine C++, I'm trying to weigh whether it's better to use TSharedPtrs or UOBJECTS() as a general strategy.

  • Which uses more memory?
  • Which has higher CPU overhead?
  • Thread safety options?

In the project currently, TSharedPtrs are used for everything that's pure data, and UOBJECTs are used for anything that may need to be handled by Blueprints.

My bias is that I'd prefer to do everything with UOBJECTs if they're equal.

  • I prefer garbage collection over ref-counting, because it's less babysitting and ceremony.
  • Ref-counting also causes memory management to happen immediately, which has caused UI hiccups on other projects, whereas garbage collection tends to happen at a different time than the user interaction.
  • I prefer to do everything with a single hammer and not mix very different approaches.
1

There are 1 answers

0
UnholySheep On BEST ANSWER

On a general level the differences are as follow:

TSharedPtr allocates an additional "control block" containing 2 integers (one for tracking strong references, one for tracking weak references) - these integer operations can be performed atomically or not (based on a template parameter) depending whether thread-safety is required. Any adding or subtracting of the count happens immediately, making the performance impact easily trackable. However, as all operations happen immediately, object deletion may happen in the middle of a critical section.

UObjects do not keep track of references themselves, however the garbage collector itself needs to keep track of all references. During a marking phase it then has to traverse these references to figure out which objects are still referenced and which can be safely collected. The performance impact is trickier to measure, as it can vary more heavily depending on amount of tracked objects and registered references (and have more cache misses from following references) - although generally speaking memory requirements are higher than for reference counting. Unreal Engine additionally restricts the GC to only run in specific intervals (by default every 60ms), which should prevent it happening during a critical section of code execution.

Therefore making a general rule of thumb is trickier, as the usage can heavily impact performance. Data is generally only loaded once and unloaded at level/game end, making any difference neglible. For the utmost performance optimization avoiding both options is usually the best choice, but can be trickier to implement (as it requires more careful thought about how the code is used)


Also worth noting is that tracing GC can easier become a bottleneck on memory constrained devices (e.g.: mobile), due to less powerful hardware. Since it's not possible to completely avoid using UObjects in Unreal, this sometimes requires manually creating "object pools" to avoid excessive GC pauses