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

12,441

Solution 1

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.

Solution 2

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.

Solution 3

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.

Solution 4

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
Share:
12,441
rlbond
Author by

rlbond

I like C++. I also like Python.

Updated on June 22, 2022

Comments

  • rlbond
    rlbond about 2 years

    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?