Can performance justify such a design choice?
With so little information, I think the advice is going to be pushed towards suggestions of profiling and measuring and warnings about premature optimization. But I'll try to give you the benefit of the doubt.
Let's apply a data-oriented design mindset which begins with the idea of the most optimal data representation, and works our way up to interfaces from there. The most useful aspect of this exercise is still going to be the public interfaces we design, the consideration of data representations is mainly to ensure that we have an interface that can leave wiggle room to optimize without cascading breakages in the system.
Since our application is performance critical, instead of copying all
attributes (possibly pointers) of Important that are needed by Small
and redefine the few methods in Small, we decided to just let Small
hold a pointer on Important.
Some assumptions I'm going to make to narrow the scope:
- There are a boatload of these
- The initialization phase where they are created as a subset of
Important's data is separate and outside of your critical code paths, not interleaved (otherwise your existing suggestion might be the best route).
- The critical path is going to loop through a boatload of these
- The fields of
Important itself, outside of the subset represented by
Small, are cold.
- The fields of
Important do not change past the initialization step (their pointees could change if there are pointer fields, but the pointer addresses would not change).
Provided that there's a one-to-one relationship of a
Small*'s subset of data fields to an
Important (no redundancy), then typically the fastest way to go is to actually copy and duplicate the data of
Important in small.
Important is cold data according to our assumptions, that allows
Small to take on a more contiguous nature, and for more of the relevant data to fit into a cache line without being intermixed with irrelevant data.
If that's wrong and there's a whole lot of redundancy between
Small* instances (i.e., a many-to-one relationship between
Small references to an
Important field), then duplicating the data could do more harm than good.
In that case, pointing to
Important can start to show a favorable advantage, as we're playing to temporal locality with
Important fields often sitting in a cache line and reducing
Small down to the size of a single pointer.
To Duplicate or Not to Duplicate
So the choice of whether or not to duplicate depends on the level of redundancy and whether the initialization phase of a small is outside of the critical paths. If you want to know to what level of redundancy, consider the total size of all
Small* instances if those fields from
Important were duplicated. If that size is not larger than the sum of all
Important fields, then copying the relevant fields to
Small might be a safe bet (provided
Important doesn't need to be accessed in critical paths at all, that it's truly cold after that point).
With little or no redundancy, and
Important being cold, then actually hot/cold field splitting with redundancy to get the hot side of the split can actually help a lot (as counter-intuitive as it may seem).
Last but not least, there should be a boatload of
Small* instances being instantiated and accessed. If there aren't, then this is worrying about something that shouldn't be worried about, as you'll likely benefit a lot from temporal locality either way.