函数

函数

函数调用可能减慢程序运行速度,原因如下:

  • 函数调用涉及到代码地址跳转与返回,可能需要约4个时间周期
  • 函数可能会导致代码分散在内存中,降低代码缓存的效率
  • 32位系统中,函数参数存储在堆栈中,传参慢
  • 需要额外时间设置栈帧,保存和恢复寄存器,还可能要保存异常处理信息
  • 函数调用需要在分支目标缓冲器占(BTB)用空间,若程序关键部分包含许多调用和分支,BTB中的竞争可能会导致分支预测错误

因此针对以上问题应该做如下优化:

  1. 避免不必要的函数
  2. 使用内联函数,内联函数会自动展开,避免调用开销,小函数通常编译器会自动内敛
  3. 避免最内存循环中的嵌套函数调用,所谓嵌套函数就是函数中中调用函数(帧函数),相对的有函数中不调用其它函数(叶函数),帧函数效率比较低,因此避免在关键部分调用帧函数
  4. 使用宏代替函数,宏也会自动展开,但是不涉及到检查等,安全性不咋地
  5. 使用fastcall函数,关键字__fastcall在32位系统中使得其使用寄存器传参
  6. 函数局部化,应该使同一模块中的函数是局部的,便于编译器优化(可以使用static关键字)
  7. 使用全程序优化,部分编译器包含的选项,使得可以在全程序的所有.cpp模块中优化寄存器分配和参数传递
  8. 使用64位模式,64位传参默认使用寄存器,64 Windows中函数前四个参数在寄存器中传递,而64 Linux中前六个整数参数和前8个浮点参数使用寄存器传递,因此更快

函数参数

大多数情况,函数参数按值传递,这意味着复制,由于寄存器的存在,简单类型的传值也很非常快。
数组总是使用指针传递
而复合类型如结构体类,除去一些特殊情况,通常使用指针或引用传递更快

函数返回值

函数返回值最最好是简单类型、指针、引用或者void,更高效
而返回符复合对象通常效率比较低下。

对于返回复合值,可以考虑以下优化:

  1. 在函数中构造复合对象
  2. 在函数中修改一个现有对象(原地修改)
  3. 使函数返回一个指向函数内部定义的静态对象的指针或者引用(存在一定风险,下一次调用该函数后,指针指向的值会变化)

函数尾调用

尾调用是指函数的最后一个语句是对另一个函数的调用,那么编译器就可以直接调转到第二个函数来替换该调用,并且第二个函数不会返回第一个函数,而是直接返回第一个函数被调用的位置,正常应该是返回第一个函数然后再返回第一个函数被调用的位置,尾调用相等于省了一层返回,效率更高。

1
2
3
4
5
void func2(int x);
void func1(int y) {
...
func2(y + 1);
}

注意类似阶乘函数不算尾调用,因为返回的还乘了一个东西

1
2
3
4
int fac(int x) {
if(x < 2) return 1;
return x * fac(x - 1);
}

但是可以通过修改改成尾调用,在递归函数中也叫尾递归

1
2
3
4
int fac(int x, int res = 1) {
if(x < 2) return res;
return fac(x - 1, x * res);
}

递归函数

递归函数通常写起来简单,但是其代驾是所有的参数和局部变量在每次递归时都有一个新实例,占用栈空间,而且深度递归还会降低返回地址的预测效率。
迭代通常要快于递归
同时递归尾调用(尾递归),不会占用额外的栈空间,通常比普通递归更快,但是依旧不如循环迭代。

重载函数

重载函数的不同版本被简单的视为不同的函数,使用重载函数没有性能损失

模板函数

模板很高效,因为模板参数总在编译时被解析,一般来说,使用模板在执行速度方面没有成本。