1

We have two C++ classes named Important and Small

The class Small use a few methods and attributes from the class Important, but not all.

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.

Can performance justify such a design choice?

  • 7
    Run a profiler on it, and see if it matters. – Robert Harvey Apr 16 '14 at 16:14
  • Well, It does. We had an Idea of creating a composition inside Important by considering that Important HAS A side A and provide only a pointer on A to Small. However this solution is not viable as there are other classes similar to small (Small2, Small3) each of them need a different part of structures hold by Important, and those even have non-nul intersections :( – Issam T. Apr 16 '14 at 16:22
  • 7
    If adequate performance is a requirement of your project, and the only way to get that adequate performance is by your design choice, then yes, the design choice is justified. Why would it be otherwise? That is the very essence of the phrase "premature optimization;" code using sensible design techniques, and then break the design only when and where you need to to achieve adequate performance. – Robert Harvey Apr 16 '14 at 16:24
0

In line with RAII, I would certainly make sure that you had a reference-counting smart pointer (shared_ptr) instead of a raw pointer. Besides that, there's nothing inherently wrong with what you've described.

However, as many people have noted already, it is impossible to give an accurate answer without two things:

  • Code. Are you turning your code into an incomprehensible mess when there is an easier option? The heck if I know. I haven't seen your code. I am not asking for code (I wouldn't read it even if you gave it to me), but I am asking you to take this advice with a grain of salt. Just because I said there was nothing wrong doesn't mean it is right.
  • Performance Numbers. Is this really something that will affect the performance of your program? Without solid numbers I can't say for sure and neither can you.
0

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.

Data-Oriented Design

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 Small* instances.
  • 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 Small instances.
  • 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).

One-to-One

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.

Since 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.

Many-to-One

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.