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
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/84761.shtml
英文地址,請注明出處:http://en.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修改注冊表 管理員身份…