I got chased out of town for asking this on StackOverflow, but it’s a real question.
What kind of memory or CPU overhead should one expect with choosing TSharedPtr or UObject for most objects, even objects that are pure data and will never be exposed to Blueprints?
The pros/cons I understand are that it’s a cleaner codebase if everything uses UObjects, and doesn’t mix memory allocation models. I’m also biased in favor of garbage collection because I feel that C++ shared pointers do their memory management at the worst possible time. We have another app that suffers because everything is shared pointers that do all their work exactly when the user interacts.
Some users point out that Epic uses UObjects throughout the engine, which should indicate that UObjects are performant. Yet, it’s clear that TSharedPtr is just a ref-counter, while UObjects much search to find and destroy references, which is more work.
Assuming I’m just making a pure data model, no blueprint connection at all, what is the penalty for doing this as UObjects over TSharedPtr? About the same? Double? Any field experience would be great.
To be clear, this data model right now is made with shared pointers. But I’m second-guessing it a lot. I don’t know if I’m doing fantasy-optimization.
1 Like
I’ve discussed this with a number of people on Reddit and Stack Overflow. Since it was difficult to find any discussion about this, I’ll post the conclusions here, in case it helps someone else.
IMPORTANT: We may be wrong, feel free to comment.
- For workloads that don’t measure in the millions of objects, don’t worry about performance.
- Garbage collection is bound to be a more expensive way to release objects. A TSharedPtr will just forget about the object when the ref-count gets to zero. A garbage collector has to search and destroy.
- Some people think that garbage collection involves a lot of cache invalidation, an underappreciated penalty.
- A UObject carries a basic 56 bytes of infrastructure. A regular C++ object / shared pointer carries only a pointer and a reference count integer. A million UObjects would still only be 56 MB.
- Unreal garbage collection isn’t native like in C#/Java/Go etc. It’s possible to create and return a result object in one function, forget it in the other function, and now you have a memory leak, which wouldn’t happen in a native GC language. Some GC-based object handling techniques like returning objects can’t be done with Unreal GC, and are better done with TSharedPtr.
- Unreal only hears about an object when it’s stored in a UPROPERTY() USomeObject* property.
- You’ll still be dealing a lot with TSharedPtr, even inside of UObjects. The only way to store a FJsonObject is as a TSharedPtr. So it’s not possible to have a single strategy for a whole app.
- TSharedPtrs are frustratingly verbose. We’ll work on some macros.
Our conclusion is where we started, and what we’ve already coded for:
- TSharedPtr are appropriate for “headless” data.
- UObjects are appropriate for things that need to touch Blueprints, networking, or receive values from editor UI.