1.普通函数调用开销
2.内联函数
1.普通函数调用开销
普通函数调用不是"直接执行函数代码", 而是需要先做"准备工作", 执行完毕后还要做"收尾工作", 再加上指令跳转的损耗
这三部分共同构成了调用专属开销("这部分开销和函数体本身的业务逻辑执行无关, 是调用动作带来的额外损耗")
1).压栈开销(调用前的准备工作)
程序执行时, 有一块专门的内存区域叫"调用栈", 每个函数调用都会在调用栈上创建一个独立的栈帧; 调用函数前, CPU必须
执行额外指令, 把两件东西放进这个栈帧里:
a.函数的参数值: 比如调用Add(3,5), 要先把3和5压入栈帧, 这样函数体内部才能读取到这两个参数
b.返回地址: 就是"当前代码执行到调用函数的下一行指令地址"; 比如: Main函数里第10行调用Add, 返回地址就是第11行,
压入栈帧是为了函数执行完后, 知道该回到哪里继续执行
这部分"压入数据"的指令执行, 就是压栈开销(额外的CPU运算和内存操作)
2).弹栈开销(调用后的收尾工作)
函数执行完毕后(比如: Add计算出结果8后), 不能直接结束, 还要做收尾, 这就是弹栈开销
a.从栈帧中取出之前压入的返回地址, 让CPU跳转到这个地址(回到调用函数的下一行继续执行)
b.释放当前函数的栈帧(把栈帧里的参数、返回地址等数据清除, 回收栈内存空间), 避免内存泄漏
这两步同样需要执行额外的CPU指令, 构成弹栈开销
3).CPU指令跳转开销
普通函数调用时, CPU的执行流程是中断式的, 不是连续的
原本CPU在按顺序执行Main函数的指令(第1行 → 第2行→…→ 第10行), 到第10行调用Add函数时, CPU需要暂停Main函数的执行
跳转到Add函数体的起始指令地址去执行; Add函数执行完后, 又要再次跳转(根据返回地址)回Main函数的第11行,这种指令地
址的跳转”会带来额外损耗; 比如CPU的"指令流水线"(提前预取指令提高效率)会被清空, 需要重新填充, 这就是跳转开销
2.内联函数
内联函数是在编译优化阶段, 直接把被调用函数的完整函数体代码, "复制粘贴"到调用它的位置, 替换掉原来的函数调用语
句; 简单说: 原来的"调用函数→压栈→跳转→弹栈→返回"的流程, 被直接替换为"原地执行函数体代码", 没有了调用”这个动作
压栈、弹栈、指令跳转这些开销自然就彻底消失
1).普通函数调用(有开销)

普通调用的执行流程: Main 第1行(调用 Add) → 压栈(3、5、Main 第 2 行地址) → 跳转至Add函数体 → 执行a + b →弹栈
(取返回地址) → 跳转回Main第2行 → 执行 Console.WriteLine
2).内联优化后(无调用开销)

内联后的执行流程: Main第1行(直接执行 3+5)→ Main 第2行(执行 Console.WriteLine), 全程没有压栈、弹栈、指令跳转
彻底消除了调用开销