C++ 中的尾调用优化TCO:原理、实战与汇编分析

C++尾调用优化

  • 什么是尾调用?
    • 描述
      • 无返回值函数最后调用函数也可能做尾调用优化
    • 例子
    • 关键特征(写法)
  • 尾调用和尾递归的区别?
  • 为什么尾调用优化可以提高效率?
    • 通常的递归调用:
    • 尾调用优化:
    • 为什么栈帧复用就可以提高效率
      • 函数调用和尾调用优化避免的开销
        • 栈空间分配
        • 栈帧入栈
        • 内存访问与缓存
  • 如何判断编译器是否做了尾调用优化?
    • 代码示例

尾调用优化(Tail Call Optimization, 简称 TCO)是现代编译器中一项重要的优化技术,它能在某些条件下避免函数调用时的栈增长,从而减少运行时内存开销,提高程序性能。

本文回答了以下几个问题:

  1. 什么是尾调用?
  2. 尾调用和尾递归的区别?
  3. 为什么尾调用优化可以提高效率?
  4. 如何判断编译器是否做了尾调用优化?

【一句话】
函数调用有栈增长的开销,尾调用优化省去了函数调用入栈的开销。

什么是尾调用?

描述

尾调用是指:一个函数在“最后一步”调用另一个函数,并将其返回值直接返回。

【补充】

无返回值函数最后调用函数也可能做尾调用优化

如果函数A是无返回值,只要函数A在最后调用函数B,最后指的是调用函数B后没有其他操作,那编译器也是有可能会做尾调用优化的
因为尾调用的关键在于函数A调用函数B后,还需不需要用到函数A中的信息,如果不需要再用了,那么也就没有了将函数A相关信息入栈的必要,也就能直接复用当前的栈帧了。

例子

int foo(int x) {return bar(x);  // ← 这是一个尾调用
}
void foo_(int x) {bar(x);  // ← 这也是一个尾调用
}

关键特征(写法)

调用另一个函数之后,不再进行其他操作,直接返回。

尾调用和尾递归的区别?

尾递归就是尾调用中最后一个函数是调用自己,形成递归。
尾递归优化,编译器实际上可能把递归函数转换为循环实现。

// 原始尾递归
int sum(int n, int acc = 0) {if (n == 0) return acc;return sum(n - 1, acc + n); // 尾递归调用
}// 优化后(编译器可能转为循环)
int sum(int n, int acc = 0) {while (n > 0) {acc += n;n--;}return acc;
}

为什么尾调用优化可以提高效率?

通常的递归调用:

每调用一次函数,就在栈上分配一个新栈帧来保存局部变量和返回地址。

尾调用优化:

编译器可以直接复用当前栈帧来执行下一个函数调用,避免了栈帧的增长。

为什么栈帧复用就可以提高效率

首先我们需要明白函数调用时发生了什么,知道了栈帧生成的开销,才能知道为什么栈帧复用可以提高效率。

函数调用和尾调用优化避免的开销

栈空间分配

每次函数调用,系统都会为该调用分配一个新的栈帧(stack frame),用来保存局部变量、返回地址、参数、寄存器状态等信息。函数返回时,这个栈帧会被销毁。

【尾调用优化】
译器做了尾调用优化,就可以复用当前函数的栈帧,直接跳转到被调用函数,而不再分配新的栈帧。这样就避免了频繁分配和释放栈帧的开销。

栈帧入栈

栈空间分配后,需要把栈帧压入栈中,而在递归调用时,很容易出现深度过大导致的栈溢出。

【尾调用优化】
尾调用优化通过复用栈帧,使得递归调用不再增加栈深度,相当于变成了循环,极大降低了栈空间需求。

内存访问与缓存

栈帧的分配和释放涉及内存操作,虽然CPU有多级缓存,但频繁的内存访问仍然影响性能。

【尾调用优化】
栈帧频繁分配释放会带来内存操作,增加缓存失效风险,复用栈帧则降低了内存访问压力,有助于提升CPU缓存命中率,进一步提升性能。

如何判断编译器是否做了尾调用优化?

我们可以通过查看生成的汇编代码来判断是否进行了优化。

生成汇编的方法可以看看我的这篇博客C++中switch-case的性能优化策略详解

代码示例

int bar(int x) {return x * 2156 + 15484;
}int foo(int x) {x++;return bar(x * 5); 
}

x86-64 gcc 编译,不开启优化

"_Z3bari":push    rbpmov     rbp, rspmov     DWORD PTR [rbp-4], edimov     eax, DWORD PTR [rbp-4]imul    eax, eax, 2156add     eax, 15484pop     rbpret
"_Z3fooi":push    rbpmov     rbp, rspsub     rsp, 8mov     DWORD PTR [rbp-4], ediadd     DWORD PTR [rbp-4], 1mov     edx, DWORD PTR [rbp-4]mov     eax, edxsal     eax, 2add     eax, edxmov     edi, eaxcall    "_Z3bari"  ; 注意在没有开启优化的情况下,是直接通过call指令调用函数,而这就会涉及到上一节讲的函数调用的开销。leaveret

开启-O2优化后

【注意】
这里有一点要提及,编译器有可能会做内联优化,这是另一种优化手段,但是本文想讨论的是尾调用优化,在函数体过于简单的情况下(例如本文提供的案例),编译器更倾向于使用内联优化,因此为了避免内联优化,我们必须对函数做一点修改,变成以下的样式,来明确规定不允许内联。

// 增加__attribute__((noinline)) 明确告知编译器不用内联【注意,这个标记是GCC和Clang支持的,MSVC或者其他编译器可能有不一样的标记】
__attribute__((noinline)) int bar(int x) {return x * 2156 + 15484;
}int foo(int x) {x++;return bar(x * 5);  // 这是一个“尾调用”!
}
"_Z3bari":imul    eax, edi, 2156add     eax, 15484ret
"_Z3fooi":lea     edi, [rdi+5+rdi*4]jmp     "_Z3bari" ; 直接跳转而非 call ⇒ 没有新栈帧产生

使用了jmp而不是call,说明这里栈帧复用,TCO 生效。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/pingmian/84761.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java集合 - ArrayList底层源码解析

下面开始对 Java 中 ArrayList 的深度源码分析,基于 JDK 8 的实现(后续版本略有差异,但核心逻辑一致)。我们将从 类结构、扩容机制、核心方法实现、性能优化、线程安全问题 等角度进行详细解析 一、类结构与核心字段 1. 类继承关…

【Qt】Qt控件

文章目录 Qt控件Layout Spacer垂直布局QVBoxLayout水平排列布局QHBoxLayout网格布局 QGridLayout表格布局 QFormLayout Button Contain命令按钮Push Button工具按钮Tool Button单选按钮Radio Button复选框按钮Check Box命令链接按钮Command Link Button按钮盒Button Box组合框G…

PHP基础-运算符

PHP 的运算符是编程中非常基础但又非常重要的一部分&#xff0c;掌握它们能让你更灵活地处理各种逻辑、计算和流程控制。 算术运算符 用于基本数学运算&#xff1a; 运算符含义示例加法$a $b-减法$a - $b*乘法$a * $b/除法$a / $b%取模$a % $b 示例&#xff1a; <?ph…

AR珠宝佩戴与传统的珠宝购物有哪些区别?​

AR 珠宝佩戴与传统的珠宝购物究竟存在着哪些显著区别呢?在传统的珠宝购物模式里&#xff0c;顾客往往需要花费时间和精力前往实体珠宝店。踏入店内&#xff0c;首先映入眼帘的便是那一排排的玻璃展柜&#xff0c;此时&#xff0c;销售人员会热情地走上前&#xff0c;小心翼翼地…

华为云CAE部署spring cloud服务

1 概述 华为云CAE&#xff08;Cloud Application Engine云应用引擎&#xff09;是一个面向WEB、微服务应用的Serverless托管服务&#xff0c;提供极速部署、极低成本、极简运维的一站式应用托管方案。支持从源码、软件包、镜像包快速发布应用&#xff0c;秒级弹性伸缩、按量付…

【技术工具】源码管理 - GIT工具

【技术工具】源码管理 - GIT工具 1 前言 之前参考语雀一位大佬的&#xff0c;但链接找不到了&#xff0c;仅供参考。 1、检查空白错误 //确认将提交的内容中有无空白信息 git diff --check 2、尝试让每一个提交成为一个逻辑的独立变更集 尽量使每笔提交都成为独立的patch&a…

Objective-c Block 面试题

以下是对我们这整段关于 Objective-C 中 Block、__block 修饰符、内存管理行为、生命周期等内容的全面总结&#xff0c;并附带了一套适合面试准备的面试题集&#xff08;带答案&#xff09;。 &#x1f9e0; 一、知识总结&#xff1a;Objective-C Block __block 修饰符 ✅ Bl…

AndroidMJ-基础-05

基础part5: 9:测试相关 postman genemotion espresso 10:性能相关 profiler 9.测试相关 espresso相关&#xff1a; Android Espresso 自动化测试指南&#xff08;Java 版&#xff09;-CSDN博客 10.性能相关 profiler相关&#xff1a; AndroidStudio之内层泄漏工具Profiler…

R语言 | 如何使用R书写html文档?

更灵活的书写方式&#xff0c;可以直接看3. 1. 可用函数 cat()函数writeLines()函数sink()函数重定向输出到HTML文件 小结&#xff1a;cat()适合简单HTML&#xff0c;writeLines()适合多行内容&#xff0c;sink()适合复杂场景。 说明&#xff1a;尽可能不用R包&#xff0c;减…

oracle 表空间超过最大限度,清理数据释放内存

目录 一、扩容&#xff1a;参考 https://blog.csdn.net/weixin_40841731/article/details/134931289 二、清理数据 1、查询文件大小情况&#xff08;管理员账号&#xff09; 2、查询表的大小&#xff08;使用该表空间的用户&#xff09; 3、清理数据&#xff08;使用该表空…

初版BL程序一些细节整理(碎碎念)

一.串口的中断触发 一般我们都是使用TXE或者RXNE来触发中断&#xff0c;其实还有完整传输结束的TC标志位和接收完成的IDLE标志位 这两个标志位有些不同&#xff0c;RXNE标志位只需要读取寄存器就会自行清除&#xff0c;但是这两个需要读取两个&#xff0c;拿IDLE举例子 这里需要…

为何京东与蚂蚁集团竞相申请稳定币牌照?

京东与蚂蚁集团竞相申请稳定币牌照&#xff0c;主要是为了抢占数字金融新赛道&#xff0c;结合香港的宽松监管政策与全球稳定币市场的快速增长。香港2023年推出的稳定币监管框架及2025年8月即将实施的《稳定币条例》&#xff0c;为企业提供了合规路径&#xff0c;吸引京东通过币…

[特殊字符] Harmony OS Next里的Web组件:网页加载的全流程掌控手册

&#x1f389; Harmony OS Next里的Web组件&#xff1a;网页加载的全流程掌控手册 ##Harmony OS Next ##Ark Ts ##教育 本文适用于教育科普行业进行学习&#xff0c;有错误之处请指出我会修改。 开发者必看的生命周期回调详解代码实操指南 作为开发者&#xff0c;你可能经常需…

【Java学习笔记】集合介绍

集合 > > 集合的引出 在之前常使用数组存储数据&#xff0c;存在的问题如下&#xff1a; &#xff08;1&#xff09;初始化时&#xff0c;长度必须指定&#xff0c;而且一旦指定&#xff0c;不能更改 &#xff08;2&#xff09;不方便扩容&#xff08;使用循环复制原…

电流传感器在汽车中的应用:从BMS电池管理到电机控制的工程解析

1 电流传感器&#xff1a;汽车电子系统的神经末梢 在现代汽车电子架构中&#xff0c;电流传感器已从简单的测量元件演变为​​关键的安全与性能组件​​。作为动力系统的“神经末梢”&#xff0c;它们持续采集电流参数并反馈至控制单元&#xff0c;构成​​实时闭环控制的基础…

积分商城拼团系统框架设计

一、逻辑分析 用户相关逻辑 用户注册与登录&#xff1a;用户需要注册账号才能参与积分商城拼团活动。注册过程中需收集必要信息&#xff0c;如用户名、密码、联系方式等。登录功能则用于验证用户身份&#xff0c;方便用户后续操作。用户积分管理&#xff1a;用户通过各种途径&a…

java 数据结构-HashMap

一、hashmap特点 1、HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。 2、HashMap 实现了 Map 接口,根据键的 HashCode 值存储数据,具有很快的访问速度,最多允许一条记录的键为 null,不支持线程同步。 3、HashMap 是无序的,即不会记录插入的顺序。 4、HashMa…

DBSyncer:一款开源的数据同步工具

DBSyncer&#xff08;简称 dbs&#xff09;是一款开源的实时数据同步中间件&#xff0c;提供 MySQL、Oracle、SQL Server、PostgreSQL、SQLite、Elasticsearch、Kafka、File、SQL 数据库等同步场景&#xff1b;支持上传插件自定义同步转换业务&#xff1b;提供监控全量和增量数…

大型语言模型的中毒攻击的系统评价

大家读完觉得有帮助记得及时关注和点赞&#xff01;&#xff01;&#xff01; 抽象 随着预训练大型语言模型 &#xff08;LLM&#xff09; 及其训练数据集的广泛使用&#xff0c;人们对与其使用相关的安全风险的担忧显著增加。 这些安全风险之一是 LLM 中毒攻击的威胁&#xff…

Windows 10更新失败解决方法

前言 在我们使用 Windows 时的时候&#xff0c;很多时候遇到系统更新 重启之后却一直提示“我们无法完成更新&#xff0c;正在撤销更改” 这种情况非常烦人&#xff0c;但其实可以通过修改文件的方法解决&#xff0c;并且正常更新到最新版操作系统 01修改注册表 管理员身份…