should I take arguments to inline functions by reference or value?
Asked Answered
T

10

29

Is one of these faster?

inline int ProcessByValue(int i)
{
    // process i somehow
}

inline int ProcessByReference(const int& i)
{
    // process i somehow
}

I know that integral types should be passed by value. However, I am concerned that the compiler might inline ProcessByValue to contain a copy. Is there a rule for this?

Toms answered 6/4, 2009 at 16:44 Comment(2)
Best thing to do here is to try it out. I doubt the compiler would do something that (seemingly) silly, but I could be wrong.Priapism
The compiler will do the work required. If a copy is needed (if i is modified inside the function) then a copy will be made. If it does not need to copy the value then it will not. Whatever happens if i is manipulated in anyway (like in an expression) it will probably be copied to a register anyway.Leighleigha
E
20

The parameter should be typed according to what makes sense for the function.

If the function takes a primitive type, pass by value would make sense. Some people I know would complain if it were passed by const ref (as it's 'unnecessary'), but I don't think I'd complain. If the function takes a user defined type and doesn't modify the parameter, then pass by const ref would make sense.

If it's a user defined type and the parameter is modified, then the semantics of the function would dictate how it should be passed.

Emmett answered 6/4, 2009 at 17:0 Comment(0)
C
29

It doesn't make a difference. In both case, the code will be inlined to the same. Needlessly copying the int (in pass-by-value) will be eliminated by the compiler, and needlessly creating a reference to the int, and following that layer of indirection when accessing the int, will also be eliminated.

Your question seems to be based on some false assumptions:

  • That the inline keyword will actually get your function inlined. (It might, but that's certainly not guaranteed)
  • That the choice of reference vs value depends on the function being inline. (The exact same performance considerations would apply to a non-inlined function)
  • That it makes a difference, and that you can outsmart the compiler with trivial changes like this (The compiler will apply the same optimizations in either case)
  • And that the optimization would actually make a measurable difference in performance. (even if it didn't, the difference would be so small as to be negligible.)

I know that integral types should be passed by value. However, I am concerned that the compiler might inline ProcessByValue to contain a copy. Is there a rule for this?

Yes, it will create a copy. Just like passing by reference would create a reference. And then, at least for simple types like ints, the compiler would eliminate both again. Inlining a function is not allowed to change the behavior of a function. If you create the function to take a value argument, it will behave as if it was given a value argument, whether or not it's inlined. If you define the function to take a reference, it will behave as if passed a reference, whether or not it's inlined. So do what leads to correct behavior.

Caseworm answered 6/4, 2009 at 17:6 Comment(6)
Does this apply to types other than int, or large objects?Adolescent
It applies to all types. Of course, for an int creating a copy is probably going to be cheaper than for large complex objects, but the rules for when the copy can be optimized away, and when it must be created are the sameCaseworm
I guess that when the function can be inlined then the copy can be also optimized. The problem is if the function cannot be inlined (for some reason) then the copy might not be optimized away and one ends up with very slow unnecessary bottleneck, just by not using const&. (I am talking for something bigger than int here). What do you think?Adolescent
I just checked. f(vector<int> v) takes twice as much time as f(const vector<int>& v) on g++ 4.9.2 with either -O2 or -O3. So, the copy is not optimized away.Bangor
@Bangor that depends on the surrounding code though. You'll have to show the entire benchmark code. :)Caseworm
@jalf I wrote an answer as not to polute the comments -- https://mcmap.net/q/481074/-should-i-take-arguments-to-inline-functions-by-reference-or-valueBangor
E
20

The parameter should be typed according to what makes sense for the function.

If the function takes a primitive type, pass by value would make sense. Some people I know would complain if it were passed by const ref (as it's 'unnecessary'), but I don't think I'd complain. If the function takes a user defined type and doesn't modify the parameter, then pass by const ref would make sense.

If it's a user defined type and the parameter is modified, then the semantics of the function would dictate how it should be passed.

Emmett answered 6/4, 2009 at 17:0 Comment(0)
R
7

The compiler should be able to optimize an inline function so that either method will generate identical code. Do the one that is clearest.

If in doubt, try it out. Turn on your compiler's assembly listing output, and see if there's a difference.

Rondeau answered 6/4, 2009 at 17:5 Comment(0)
B
3

Pass by value if the type is smaller than or comparable to a pointer; for example, int, char, double, small structs, ...

Pass by reference for larger objects; for example, STL containers. I've read a lot about compilers being able to optimize it but they didn't at my simple benchmark that follows. Unless you want to waste time testing use cases, use const T& obj.

Bonus: For faster speed use restrict from c99 (this way you catch up to fortran, which restricts pointer aliasing; use case: f(const T&__restrict__ obj). C++ standard doesn't allow restrict keyword but compilers use internal keywords -- g++ uses __restrict__. If there is no aliasing in the code, there is no speed gain.

the benchmark with g++ 4.9.2:

Passing vector by reference:

> cat inpoint.cpp
#include <vector>
#include <iostream>

using namespace std;

inline int show_size(const vector<int> &v) {
  return v.size();
}

int main(){
  vector<int> v(100000000);
  cout << show_size(v) << endl;
  return 0;
}
> g++ -std=c++14 -O2 inpoint.cpp; time ./a.out
100000000

real    0m0.330s
user    0m0.072s
sys     0m0.256s

Passing vector by value takes twice as much time:

> cat invalue.cpp
#include <vector>
#include <iostream>

using namespace std;

inline int show_size(vector<int> v) {
  return v.size();
}

int main(){
  vector<int> v(100000000);
  cout << show_size(v) << endl;
  return 0;
}
> g++ -std=c++14 -O2 invalue.cpp; time ./a.out
100000000

real    0m0.985s
user    0m0.204s
sys     0m0.776s
Bangor answered 6/3, 2015 at 19:40 Comment(0)
C
2

Lets take 2 cases :-

void bit_twiddle(char& c);

// usage on some char array :-
for(...) bit_twiddle(array[i]);
char bit_twiddle(char c);

// usage on some char array :-
for(...) array[i] = bit_twiddle(array[i]);

Here's a Benchmark I Did (runtime in seconds for 100MiB array) :-

      +----------------------------------------+
      |      clang++14     |       g++11       |
      +----------------------------------------+
      | -O0  | -O1  | -O2  | -O0  | -O1 | -O2  |
      +----------------------------------------+
ref-> | 10.2 | 0.84 | 0.29 | 10.5 | 2.8 | 1.43 |
val-> |  8.3 | 0.84 | 0.29 | 7.76 | 2.3 | 1.43 |
      +----------------------------------------+

Conclusion :-

  • I this case, pass by value is faster with compiler optimisations turned off.

  • clang++ optimizes away reference/value parity at -O1, meanwhile g++ does the same at -O2 in my tests.

  • So in the end with Compiler optimisations, it doesn't matter which way you use.

Chronological answered 23/4, 2022 at 15:12 Comment(0)
S
0

The best way to figure this out is to create a testbed that does both, build optimized versions of the code, and check it out the assembly. You'll see immediately what's going on with your particular compiler and your particular use-case.

When it really gets down to it, do what you think a user of your class would expect from an interface. When you have it all built and working, measure and find out where your bottlenecks are. Chances are, any difference this might make (and it's unlikely to make any) will be drowned out by larger performance concerns elsewhere in your code.

Sow answered 6/4, 2009 at 17:6 Comment(0)
E
0

If your compiler isn't smart enough to optimize away the local copy that isn't modified, it probably isn't smart enough to optimize away the local reference. In which case it will be generating even more awful code for the pass-by-reference case (cause every access to be indirect).

Edwyna answered 6/4, 2009 at 17:19 Comment(0)
H
0

A very short answer: when deciding whether to pass by reference or by value treat the inline and non-inline functions the same.

Hagan answered 6/4, 2009 at 18:43 Comment(0)
R
0

In the case of primitives it doesn't matter because you are only passing 4 bytes.

The reason for passing a reference is because it is 4 bytes in size and that's a drastic reduction size in the case of custom types and large strings.

The argument is for speed... usually.

In the case of an inline function, you'd want all types that aren't primitives to be passed by reference since you're telling the compiler to inline it in the first place.

Reprovable answered 7/4, 2009 at 5:34 Comment(0)
A
-1

In general,

Only declare output primitives as references.

Only declare an input primitive as a reference or const ref if you need to disallow expressions:

int two = plus1( 1 );  //  compile error if plus1 is declared as "int plus1( int& )"

double y = sqrt( 1.1 * 2 );  // compile error if sqrt is declared as "double sqrt( const double& )"
Allure answered 28/9, 2011 at 14:31 Comment(1)
Why do you think that double sqrt(const double&) is incompatible with sqrt(1.1 * 2)?Ro

© 2022 - 2024 — McMap. All rights reserved.