Jonathan H
Jonathan H

Reputation: 7962

Passing scalar types by value or reference: does it matter?

Granted, micro-optimization is stupid and probably the cause of many mistakes in practice. Be that as it may, I have seen many people do the following:

void function( const double& x ) {}

instead of:

void function( double x ) {}

because it was supposedly "more efficient". Say that function is called ridiculously often in a program, millions of times; does this sort of "optimisation" matter at all?

Upvotes: 8

Views: 4386

Answers (4)

AnT stands with Russia
AnT stands with Russia

Reputation: 320671

Passing by reference in this case is certainly not more efficient by itself. Note that qualifying that reference with a const does not mean that the referenced object cannot change. Moreover, it does not mean that the function itself cannot change it (if the referee is not constant, then the function it can legally use const_cast to get rid of that const). Taking that into account, it is clear that passing by reference forces the compiler to take into account possible aliasing issues, which in general case will lead to generation of [significantly] less efficient code in pass-by-reference case.

In order to take possible aliasing out of the picture, one'd have to begin the latter version with

void function( const double& x ) {
  double non_aliased_x = x;
  // ... and use `non_aliased_x` from now on
  ...
}

but that would defeat the proposed reasoning for passing by reference in the first place.

Another way to deal with aliasing would be to use some sort of C99-style restrict qualifier

void function( const double& restrict x ) {

but again, even in this case the cons of passing by reference will probably outweigh the pros, as explained in other answers.

Upvotes: 3

leemes
leemes

Reputation: 45715

Unless the function is inlined, and depending on the calling convention (the following assumes stack-based parameter passing, which in modern calling conventions is only used when the function has too many arguments*), there are two differences in how the argument is passed and used:

  • double: The (probably) 8 byte large value is written onto the stack and read by the function as is.
  • double & or double *: The value lies somewhere in the memory (might be "near" the current stack pointer, e.g. if it's a local variable, but might also be somewhere far away). A (probably) 4 or 8 byte large pointer address (32 bit or 64 bit system respectively) is stored on the stack and the function needs to dereference the address to read the value. This also requires the value to be in addressable memory, which registers aren't.

This means, the stack space required to pass the argument might be a little bit less when using references. This not only decreases memory requirement but also cache efficiency of the topmost bytes of the stack. When using references, dereferencing adds some piece of work more to do.

To summarize, use references for large types (let's say when sizeof(T) > 32 or maybe even more). When stack size and hotness plays a very important role maybe already if sizeof(T) > sizeof(T*).


*) See the comments on this and SOReader's answer for what's happening if this is not the case.

Upvotes: 2

Mgetz
Mgetz

Reputation: 5138

Long story short no, and particularly not on most modern platforms where scalar and even floating point types are passed via register. The general rule of thumb I've seen bandied about is 128bytes as the dividing line between when you should just pass by value and pass by reference.

Given the fact that the data is already stored in a register you're actually slowing things down by requiring the processor to go out to cache/memory to get the data. That could be a huge hit depending on if the cache line the data is in is invalid.

At the end of the day it really depends on what the platform ABI and calling convention is. Most modern compilers will even use registers to pass data structures if they will fit (e.g. a struct of two shorts etc.) when optimization is turned up.

Upvotes: 12

SOReader
SOReader

Reputation: 6037

In the latter example you save 4B of being copied to stack during function call. It takes 8B to store doubles and only 4B to store a pointer (in 32b environment, in 64b it takes 64b=8B so you don't save anything) or a reference which is nothing more than a pointer with a bit of compiler support.

Upvotes: 2

Related Questions