在大多数架构中,函数调用的成本包括将所有寄存器保存到堆栈中(或者部分保存,或者不保存),将函数参数推入堆栈(或者放入寄存器中),增加堆栈指针并跳转到新代码的开头。然后当函数完成时,您必须从堆栈中恢复寄存器。这个网页描述了各种调用约定所涉及的内容。
现在,大多数C++编译器都足够聪明,可以为您内联函数。inline关键字只是对编译器的提示。有些编译器甚至会跨翻译单位进行内联,如果他们认为这有帮助。
我对一个简单的自增函数进行了简单的基准测试:
inc.c:
typedef unsigned long ulong;
ulong inc(ulong x){
return x+1;
}
main.c
#include <stdio.h>
#include <stdlib.h>
typedef unsigned long ulong;
#ifdef EXTERN
ulong inc(ulong);
#else
static inline ulong inc(ulong x){
return x+1;
}
#endif
int main(int argc, char** argv){
if (argc < 1+1)
return 1;
ulong i, sum = 0, cnt;
cnt = atoi(argv[1]);
for(i=0;i<cnt;i++){
sum+=inc(i);
}
printf("%lu\n", sum);
return 0;
}
在我的 Intel(R) Core(TM) i5 CPU M 430 @ 2.27GHz 上运行10亿次迭代,我得到了以下结果:
(看起来可能会波动多达0.2秒,但我懒得计算正确的标准偏差,也不关心它们)
这表明,在此计算机上函数调用的开销约为3纳秒
我测量的最快速度大约为0.3ns,这意味着一个函数调用的成本约为9个原始操作,非常简单地说。
对于通过PLT(共享库中的函数)调用的函数,每次调用将增加2纳秒的开销(总调用时间约为6纳秒)。
有技术层面和实际层面两种回答。实际上,这个问题永远不会成为问题,在极少数情况下,唯一的方法是通过实际的分析测试。
你所提到的技术层面的答案通常并不相关,因为编译器进行了优化。但如果你仍然感兴趣,可以参考Josh的详细描述。
如果想要知道“百分比”,你需要知道函数本身的开销有多大。除了调用的函数成本之外,没有任何百分比,因为你正在与零成本操作进行比较。对于内联代码,没有成本,处理器只需移动到下一条指令。内联的缺点是代码大小更大,这表现在堆栈构建/拆卸成本的不同方式。
开销的大小取决于编译器、CPU 等因素。内联时百分比的开销取决于你要内联的代码。唯一的方法是对你自己的代码进行两种方式的分析 - 这就是为什么没有一个明确的答案。
对于非常小的函数,内联是有意义的,因为函数调用的(小)成本相对于函数体的(非常小)成本来说是显著的。但对于大多数超过几行的函数,内联并不能带来很大的优势。
Foo::result_type MakeMeFaster()
{
Foo t = 0;
for (auto i = 0; i < 1000; ++i)
t += CheckOverhead(SomethingUnpredictible());
return t.result();
}
Foo CheckOverhead(int i)
{
auto n = CalculatePi_1000_digits();
return i * n;
}
Foo::result_type MakeMeFaster()
{
Foo t;
auto _hidden_optimizer_tmp = CalculatePi_1000_digits();
for (auto i = 0; i < 1000; ++i)
t += SomethingUnpredictible() * _hidden_optimizer_tmp;
return t.result();
}
看起来调用开销减少了,因为它确实将函数的大块部分(即CalculatePi_1000_digits的调用)提升出循环。编译器需要能够证明CalculatePi_1000_digits始终返回相同的结果,但是优秀的优化器可以做到这一点。
在小型(可内联)函数或类中,开销非常小。
以下示例有三个不同的测试,每个测试都会运行多次并计时。结果始终等于几千分之一单位时间的顺序。
#include <boost/timer/timer.hpp>
#include <iostream>
#include <cmath>
double sum;
double a = 42, b = 53;
//#define ITERATIONS 1000000 // 1 million - for testing
//#define ITERATIONS 10000000000 // 10 billion ~ 10s per run
//#define WORK_UNIT sum += a + b
/* output
8.609619s wall, 8.611255s user + 0.000000s system = 8.611255s CPU(100.0%)
8.604478s wall, 8.611255s user + 0.000000s system = 8.611255s CPU(100.1%)
8.610679s wall, 8.595655s user + 0.000000s system = 8.595655s CPU(99.8%)
9.5e+011 9.5e+011 9.5e+011
*/
#define ITERATIONS 100000000 // 100 million ~ 10s per run
#define WORK_UNIT sum += std::sqrt(a*a + b*b + sum) + std::sin(sum) + std::cos(sum)
/* output
8.485689s wall, 8.486454s user + 0.000000s system = 8.486454s CPU (100.0%)
8.494153s wall, 8.486454s user + 0.000000s system = 8.486454s CPU (99.9%)
8.467291s wall, 8.470854s user + 0.000000s system = 8.470854s CPU (100.0%)
2.50001e+015 2.50001e+015 2.50001e+015
*/
// ------------------------------
double simple()
{
sum = 0;
boost::timer::auto_cpu_timer t;
for (unsigned long long i = 0; i < ITERATIONS; i++)
{
WORK_UNIT;
}
return sum;
}
// ------------------------------
void call6()
{
WORK_UNIT;
}
void call5(){ call6(); }
void call4(){ call5(); }
void call3(){ call4(); }
void call2(){ call3(); }
void call1(){ call2(); }
double calls()
{
sum = 0;
boost::timer::auto_cpu_timer t;
for (unsigned long long i = 0; i < ITERATIONS; i++)
{
call1();
}
return sum;
}
// ------------------------------
class Obj3{
public:
void runIt(){
WORK_UNIT;
}
};
class Obj2{
public:
Obj2(){it = new Obj3();}
~Obj2(){delete it;}
void runIt(){it->runIt();}
Obj3* it;
};
class Obj1{
public:
void runIt(){it.runIt();}
Obj2 it;
};
double objects()
{
sum = 0;
Obj1 obj;
boost::timer::auto_cpu_timer t;
for (unsigned long long i = 0; i < ITERATIONS; i++)
{
obj.runIt();
}
return sum;
}
// ------------------------------
int main(int argc, char** argv)
{
double ssum = 0;
double csum = 0;
double osum = 0;
ssum = simple();
csum = calls();
osum = objects();
std::cout << ssum << " " << csum << " " << osum << std::endl;
}
sum += std::sqrt(a*a + b*b + sum) + std::sin(sum) + std::cos(sum)
8.485689s wall, 8.486454s user + 0.000000s system = 8.486454s CPU (100.0%)
8.494153s wall, 8.486454s user + 0.000000s system = 8.486454s CPU (99.9%)
8.467291s wall, 8.470854s user + 0.000000s system = 8.470854s CPU (100.0%)
2.50001e+015 2.50001e+015 2.50001e+015
sum += a + b