2

I am working on a C++ project with high performance requirements. The data between some classes is shared via structures. This means several classes have a reference to the same structures and use them. My question is:

Is the performance of this code:

void foo(&myStruct)
{
  //...
  int var = myStruct.varA; // access struct by reference
  //...
}

better or worse than this code?

class.h

int* temp; //member variable

class.cpp

void init(&myStruct)
{
  // Called while booting - no high performance requirements
  temp = &myStruct.varA;
  //...
}

void foo()
{
  //...
  int var = *temp; // access struct by helper variable
  //...
}
Gerhardh
  • 11,688
  • 4
  • 17
  • 39
BenRa27
  • 29
  • 3
  • Your `class.cpp` contains invalid code and will not compile. By definition, valid code is better than invalid code. – Peter Sep 20 '22 at 05:06
  • 1
    The first one saves you one copy of a pointer and is more clear. In both cases you're copying an int in `foo`. But the difference is neglectable. See https://godbolt.org/z/dPxb8rKeM vs https://godbolt.org/z/176KEfx1M. – Tohnmeister Sep 20 '22 at 05:08
  • When managing shared resources use [`std::shared_ptr`](https://en.cppreference.com/w/cpp/memory/shared_ptr). – oraqlle Sep 20 '22 at 06:15
  • @oraqlle only if the ownership is shared – Alan Birtles Sep 20 '22 at 07:31
  • 1
    @Tohnmeister Wow, awesome web tool! Thanks for the Info, I will do some tests to decide what to do. – BenRa27 Sep 20 '22 at 08:00
  • @AlanBirtles True, but if not then a [`std::weak_ptr`](https://en.cppreference.com/w/cpp/memory/weak_ptr) can be used for the non-owners. – oraqlle Sep 20 '22 at 10:48
  • @oraqlle The question is about which solution is more performant in a system which has high performance requirements and not on how to ideally handle ownership. If somebody is concerned about the difference between accessing a dereferenced pointer vs a struct-member, then surely they will not want to use a RAII object with atomic reference counting. – Tohnmeister Sep 20 '22 at 12:28
  • @oraqlle `wesk_ptr` still implies ownership information, if there's only a single owner there's no need for `shared_ptr`, the owner can use `unique_ptr` or no no pointer at all and pass to others by raw pointer or reference – Alan Birtles Sep 20 '22 at 13:31
  • @Tohnmeister You make a good point but I wasn't trying to answer the question but add supporting ideas about "smart" shared resource models in C++. It is true that there is overhead in RAII classes but it's mostly minimal but I can see how it might deter one from using them in high performance applications but poorly managed resources can be even worse. Also shared only tolls on initial construction, any new shared is simple as the resource already exists. – oraqlle Sep 20 '22 at 13:34
  • @AlanBirtles weak_ptr is a non-owning pointer to a shared_ptr that assumes temporary ownership when access is needed. I mention shared_ptr as BenRa27 mentioned the structs are shared between the classes not necessarily owned by a single class. Plus you can't copy a unique_ptr only move or allocate a new resource. – oraqlle Sep 20 '22 at 13:39

1 Answers1

2

Both implementations are probably equally efficient, but the second one is uglier and more bug-prone, so I would suggest to avoid it.

On first glance, your second implementation appears more efficient because in the first one, the code needs to add a small offset to the pointer before dereferencing it. However, since such code is extremely common, modern processors usually support dereferencing with an offset as a single instruction. For example in x86 assembly, you can add a constant offset to a pointer stored in a register when dereferencing it with the MOV instruction.

However, the fact that the offset and dereference happens in a single instruction doesn't prove that it is exactly as quick as a single instruction that doesn't need an offset. The x86 instruction set is full of instructions which take different amount of cycles depending on their operands. I found an earlier stackoverflow answer Do complex addressing modes have extra overhead for loads from memory? which suggests that the offset is already part of the most basic and quickest variant of MOV, and only more complex addressing (such as needing to add two registers together) makes dereferencing a tiny bit slower.

So all-in-all, I would choose the first option. It's much clearer, less error-prone, and probably identically efficient. And if you're not sure, you can always measure it for yourself and report back here.

Nadav Har'El
  • 11,785
  • 1
  • 24
  • 45
  • Thank you for the detailed answer. I would also prefer the first solution for the reasons you mentioned. I have already done some measurements today and the performance improvement of the second implementation is very slight. I hope I will not need it in the end. – BenRa27 Sep 20 '22 at 18:22