简介
编译器中的函数内联是一种优化技术,用于减少函数调用的开销。当函数被标记为内联时,编译器会将该函数的代码副本直接插入到每个调用该函数的地方,而不需要在运行时跳转到函数代码,这样可以提高代码的执行效率。
int add(int a,int b)
{
return a + b;
}
int fun()
{
int a = 2;
int b = 3;
a = add(a, b);
}
内联后
int fun()
{
int a = 2;
int b = 3;
a = a + b;
}
原理
调用图
调用图(call graph),就是告诉我们哪个函数调用了那个函数的图。一个程序的调用图是一个节点和边的集合,并满足
1.对程序中的每一个函数都有一个节点。
2.对于每个调用点(call site)都有一个节点。所谓调用点就是程序中调用某个函数的一个位置。
3.如果调用点c调用了函数p,就存在一条从c的节点到p的节点的边。
调用图其实是一个图结构数据。
以下是一个简单示例:
void fun0()
{
}
void fun1()
{
fun0();
}
void fun2()
{
}
int main()
{
fun1();
fun2();
}
调用图如下所示:
这里一共有3条边,如上图标号所示。
使用深度优先遍历:
节点id:3的from节点是fun0函数,to节点是fun1。
节点id:1的from节点是fun1函数,to节点是main。
节点id:2的from节点是fun2函数,to节点是main。
节点id:0的from节点是main函数,to节点是NULL。
实现
1.优化调用深度,对于调用深度大于3的函数块进行内联。目的是减少调用调用深度,增加程序执行效率。
2.如果函数有标志inline,那么把函数添加到候选列表。
3.如果被调用函数调用点只有一个,那么把函数添加到候选列表。
4.内联所有候选列表中的函数。
内联函数
1.将被调用者函数中的所有指令放到集合A中。
2.找到被调用者函数的调用者函数,从调用者函数中找到其中调用点call。
3.被调用者函数参数提升成全局变量,局部变量提升成调用者函数中的局部变量。
4.将调用点call改变为label指令。
5.将集合A中的所有指令插入到调用点label指令之前。
6.更新调用参数。
例子
int add(int a,int b)
{
return a + b;
}
int main()
{
int a = 2;
int b = 3;
int c = 0;
c = add(a, b);
}
fun add
param0 a ==> R0
param1 b ==> R1
param2 out ==> R2
LABEL0:
ADD R3, R0, R1
MOV R2, R3
RET
fun main
local a ==> R4
local b ==> R5
local c ==> R6
MOV R4, 2
MOV R5, 3
MOV R6, 0
MOV R0, R4
MOV R1, R5
CALL add
MOV R6, R2
内联后
fun main
local a ==> R4
local b ==> R5
local c ==> R6
MOV R4, 2
MOV R5, 3
MOV R6, 0
ADD R6, R4, R5