28

其中之一更快吗?

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

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

我知道整数类型应该按值传递。但是,我担心编译器可能会内联 ProcessByValue 以包含副本。有这方面的规则吗?

4

9 回答 9

27

这没什么区别。在这两种情况下,代码都将被内联。编译器将消除不必要地复制 int(按值传递),并且不必要地创建对 int 的引用,并在访问 int 时遵循该间接层,也将被消除。

您的问题似乎是基于一些错误的假设:

  • inline 关键字实际上会使您的函数内联。(可能,但这当然不能保证)
  • 参考与值的选择取决于内联的函数。(完全相同的性能考虑将适用于非内联函数)
  • 它有所作为,并且您可以通过像这样的微不足道的更改来超越编译器(编译器将在任何一种情况下应用相同的优化)
  • 并且优化实际上会在性能上产生可衡量的差异。(即使没有,差异也会很小,可以忽略不计。)

我知道整数类型应该按值传递。但是,我担心编译器可能会内联 ProcessByValue 以包含副本。有这方面的规则吗?

是的,它会创建一个副本。就像通过引用传递会创建一个引用一样。然后,至少对于像 int 这样的简单类型,编译器会再次消除两者。内联函数不允许改变函数的行为。如果你创建函数来接受一个值参数,它的行为就好像它被赋予了一个值参数,无论它是否被内联。如果您将函数定义为获取引用,则无论它是否内联,它都会表现得好像传递了一个引用。所以做导致正确行为的事情。

于 2009-04-06T17:06:19.053 回答
20

应根据对函数有意义的内容键入参数。

如果函数采用原始类型,则按值传递是有意义的。我认识的一些人会抱怨如果它被 const ref 通过(因为它是“不必要的”),但我认为我不会抱怨。如果函数采用用户定义的类型并且不修改参数,那么通过 const ref 传递将是有意义的。

如果它是用户定义的类型并且参数被修改,那么函数的语义将决定它应该如何传递。

于 2009-04-06T17:00:04.123 回答
7

编译器应该能够优化内联函数,以便任一方法生成相同的代码。做一个最清楚的。

如果有疑问,请尝试一下。打开编译器的汇编列表输出,看看是否有区别。

于 2009-04-06T17:05:27.857 回答
3

如果类型小于或可比指针,则按值传递;例如,int、char、double、小型结构、...

通过引用传递较大的对象;例如,STL 容器。我已经阅读了很多关于编译器能够对其进行优化的信息,但在我接下来的简单基准测试中它们并没有。除非您想浪费时间测试用例,否则请使用const T& obj.

奖励:从 c99 使用更快的速度restrict(这样你就可以赶上 fortran,它限制指针别名;用例:f(const T&__restrict__ obj)。C++ 标准不允许restrict关键字,但编译器使用内部关键字 - g++ 使用__restrict__。如果在代码,没有速度增益。

g++ 4.9.2 的基准测试:

通过引用传递向量:

> 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

按值传递向量需要两倍的时间:

> 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
于 2015-03-06T19:40:29.393 回答
0

解决这个问题的最好方法是创建一个测试平台,它可以同时执行这两种操作,构建代码的优化版本,然后检查它的程序集。您将立即看到您的特定编译器和特定用例发生了什么。

当它真正开始时,做你认为你的类的用户对接口的期望。当你把它全部建成并工作时,测量并找出你的瓶颈在哪里。很有可能,这可能产生的任何差异(而且不太可能产生任何差异)将被代码中其他地方更大的性能问题所淹没。

于 2009-04-06T17:06:39.723 回答
0

如果您的编译器不够聪明,无法优化掉未修改的本地副本,那么它可能不够聪明,无法优化掉本地引用。在这种情况下,它将为传递引用的情况生成更糟糕的代码(导致每次访问都是间接的)。

于 2009-04-06T17:19:11.270 回答
0

一个非常简短的回答:在决定是按引用传递还是按值传递时,将内联和非内联函数视为相同。

于 2009-04-06T18:43:09.960 回答
0

在原语的情况下,这并不重要,因为您只传递了 4 个字节。

传递引用的原因是因为它的大小为 4 个字节,并且在自定义类型和大字符串的情况下,这会大大减少大小。

争论是为了速度……通常。

对于内联函数,您希望所有不是原语的类型都通过引用传递,因为您首先告诉编译器内联它。

于 2009-04-07T05:34:40.207 回答
-1

一般来说,

仅将输出原语声明为引用。

如果您需要禁止表达式,则仅将输入原语声明为引用或 const ref:

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& )"
于 2011-09-28T14:31:59.413 回答