編譯器控制技術降低缺失率
❶ 關於c++定義PI,後運算結果精度缺失。
這是編譯器的優化
想要得到精確的小數點後位數控制
頭文件 #include<iomanip>
然後輸出時
cout << fixed << setprecision(n) << num;
n是要保留的小數位數
❷ pcm編譯器系統實驗過程中發現的問題
1. 點到點PCM多路電話通信原理
脈沖編碼調制(PCM)技術與增量調制(ΔM)技術已經在數字通信系統中得到廣泛應用。當信道雜訊比較小時一般用PCM,否則一般用ΔM。目前速率在155MB以下的准同步數字系列(PDH)中,國際上存在A解和μ律兩種PCM編解碼標准系列,在155MB以上的同步數字系列(SDH)中,將這兩個系列統一起來,在同一個等級上兩個系列的碼速率相同。而ΔM在國際上無統一標准,但它在通信環境比較惡劣時顯示了巨大的優越性。
點到點PCM多路電話通信原理可用圖9-1表示。對於基帶通信系統,廣義信道包括傳輸媒質、收濾波器、發濾波器等。對於頻帶系統,廣義信道包括傳輸媒質、調制器、解調器、發濾波器、收濾波器等。
本實驗模塊可以傳輸兩路話音信號。採用TP3057編譯器,它包括了圖9-1中的收、發低通濾波器及PCM編解碼器。編碼器輸入信號可以是本實驗模塊內部產生的正弦信號,也可以是外部信號源的正弦信號或電話信號。本實驗模塊中不含電話機和混合電路,廣義信道是理想的,即將復接器輸出的PCM信號直接送給分接器。
2. PCM編解碼模塊原理
本模塊的原理方框圖圖9-2所示,電原理圖如圖9-3所示(見附錄),模塊內部使用+5V和-5V電壓,其中-5V電壓由-12V電源經7905變換得到。
圖9-2 PCM編解碼原理方框圖
該模塊上有以下測試點和輸入點:
• BS PCM基群時鍾信號(位同步信號)測試點
• SL0 PCM基群第0個時隙同步信號
• SLA 信號A的抽樣信號及時隙同步信號測試點
• SLB 信號B的抽樣信號及時隙同步信號測試點
• SRB 信號B解碼輸出信號測試點
• STA 輸入到編碼器A的信號測試點
• SRA 信號A解碼輸出信號測試點
• STB 輸入到編碼器B的信號測試點
• PCM PCM基群信號測試點
• PCM-A 信號A編碼結果測試點
• PCM-B 信號B編碼結果測試點
• STA-IN 外部音頻信號A輸入點
• STB-IN 外部音頻信號B輸入點
本模塊上有三個開關K5、K6和K8,K5、K6用來選擇兩個編碼器的輸入信號,開關手柄處於左邊(STA-IN、STB-IN)時選擇外部信號、處於右邊(STA-S、STB-S)時選擇模塊內部音頻正弦信號。K8用來選擇SLB信號為時隙同步信號SL1、SL2、SL5、SL7中的某一個。
圖9-2各單元與電路板上元器件之間的對應關系如下:
•晶振 U75:非門74LS04;CRY1:4096KHz晶體
•分頻器1 U78:A:U78:D:觸發器74LS74;U79:計數器74LS193
•分頻器2 U80:計數器74LS193;U78:B:U78:D:觸發器74LS74
•抽樣信號產生器 U81:單穩74LS123;U76:移位寄存器74LS164
•PCM編解碼器A U82:PCM編解碼集成電路TP3057(CD22357)
•PCM編解碼器B U83:PCM編解碼集成電路TP3057(CD22357)
•幀同步信號產生器 U77:8位數據產生器74HC151;U86:A:與門7408
•正弦信號源A U87:運放UA741
•正弦信號源B U88:運放UA741
•復接器 U85:或門74LS32
晶振、分頻器1、分頻器2及抽樣信號(時隙同步信號)產生器構成一個定時器,為兩個PCM編解碼器提供2.048MHz的時鍾信號和8KHz的時隙同步信號。在實際通信系統中,解碼器的時鍾信號(即位同步信號)及時隙同步信號(即幀同步信號)應從接收到的數據流中提取,方法如實驗五及實驗六所述。此處將同步器產生的時鍾信號及時隙同步信號直接送給解碼器。
由於時鍾頻率為2.048MHz,抽樣信號頻率為8KHz,故PCM-A及PCM-B的碼速率都是2.048MB,一幀中有32個時隙,其中1個時隙為PCM編碼數據,另外31個時隙都是空時隙。
PCM信號碼速率也是2.048MB,一幀中的32個時隙中有29個是空時隙,第0時隙為幀同步碼(×1110010)時隙,第2時隙為信號A的時隙,第1(或第5、或第7 —由開關K8控制)時隙為信號B的時隙。
本實驗產生的PCM信號類似於PCM基群信號,但第16個時隙沒有信令信號,第0時隙中的信號與PCM基群的第0時隙的信號也不完全相同。
由於兩個PCM編解碼器用同一個時鍾信號,因而可以對它們進行同步復接(即不需要進行碼速調整)。又由於兩個編碼器輸出數據處於不同時隙,故可對PCM-A和PCM-B進行線或。本模塊中用或門74LS32對PCM-A、PCM-B及幀同步信號進行復接。在解碼之前,不需要對PCM進行分接處理,解碼器的時隙同步信號實際上起到了對信號分路的作用。
3. TP3057簡介
本模塊的核心器件是A律PCM編解碼集成電路TP3057,它是CMOS工藝製造的專用大規模集成電路,片內帶有輸出輸入話路濾波器,其引腳及內部框圖如圖9-4、圖9-5所示。引腳功能如下:
圖9-4 TP3057引腳圖
(1) V一 接-5V電源。
(2) GND 接地。
(3) VFRO 接收部分濾波器模擬信號輸出端。
(4) V+ 接+5V電源。
(5) FSR 接收部分幀同信號輸入端,此信號為8KHz脈沖序列。
(6) DR 接收部分PCM碼流輸入端。
(7) BCLKR/CLKSEL 接收部分位時鍾(同步)信號輸入端,此信號將PCM碼流在FSR上升沿後逐位移入DR端。位時鍾可以為64KHz到2.048MHz的任意頻率,或者輸入邏輯「1」或「0」電平器以選擇1.536MHz、1.544MHz或2.048MHz用作同步模式的主時鍾,此時發時鍾信號BCLKX同時作為發時鍾和收時鍾。
(8) MCLKR/PDN 接收部分主時鍾信號輸入端,此信號頻率必須為1.536MHz、1.544MHz或2.048MHz。可以和MCLKX非同步,但是同步工作時可達到最佳狀態。當此端接低電平時,所有的內部定時信號都選擇MCLKX信號,當此端接高電平時,器件處於省電狀態。
(9) MCLKX 發送部分主時鍾信號輸入端,此信號頻率必須為1.536MHz、1.544MHz或2.048MHz。可以和MCLKR非同步,但是同步工作時可達到最佳狀態。
(10) BCLKX 發送部分位時鍾輸入端,此信號將PCM碼流在FSX信號上升沿後逐位移出DX端,頻率可以為64KHz到2.04MHz的任意頻率,但必須與MCLKX同步。
圖9-5 TP3057內部方框圖
(11) DX 發送部分PCM碼流三態門輸出端。
(12) FSX 發送部分幀同步信號輸入端,此信號為8KHz脈沖序列。
(13) TSX 漏極開路輸出端,在編碼時隙輸出低電平。
(14) GSX 發送部分增益調整信號輸入端。
(15) VFXi- 發送部分放大器反向輸入端。
(16) VFXi+ 發送部分放大器正向輸入端。
TP3057由發送和接收兩部分組成,其功能簡述如下。
發送部分:
包括可調增益放大器、抗混淆濾波器、低通濾波器、高通濾波器、壓縮A/D轉換器。抗混淆濾波器對采樣頻率提供30dB以上的衰減從而避免了任何片外濾波器的加入。低通濾波器是5階的、時鍾頻率為128MHz。高通濾波器是3階的、時鍾頻率為32KHz。高通濾波器的輸出信號送給階梯波產生器(采樣頻率為8KHz)。階梯波產生器、逐次逼近寄存器(S•A•R)、比較器以及符號比特提取單元等4個部分共同組成一個壓縮式A/D轉換器。S•A•R輸出的並行碼經並/串轉換後成PCM信號。參考信號源提供各種精確的基準電壓,允許編碼輸入電壓最大幅度為5VP-P。
發幀同步信號FSX為采樣信號。每個采樣脈沖都使編碼器進行兩項工作:在8比特位同步信號BCLKX的作用下,將采樣值進行8位編碼並存入逐次逼近寄存器;將前一采樣值的編碼結果通過輸出端DX輸出。在8比特位同步信號以後,DX端處於高阻狀態。
接收部分:
包括擴張D/A轉換器和低通濾波器。低通濾波器符合AT&T D3/D4標准和CCITT建議。D/A轉換器由串/並變換、D/A寄存器組成、D/A階梯波形成等部分構成。在收幀同步脈沖FSR上升沿及其之後的8個位同步脈沖BCLKR作用下,8比特PCM數據進入接收數據寄存器(即D/A寄存器),D/A階梯波單元對8比特PCM數據進行D/A變換並保持變換後的信號形成階梯波信號。此信號被送到時鍾頻率為128KHz的開關電容低通濾波器,此低通濾波器對階梯波進行平滑濾波並對孔徑失真(sinx)/x進行補嘗。
在通信工程中,主要用動態范圍和頻率特性來說明PCM編解碼器的性能。
動態范圍的定義是解碼器輸出信噪比大於25dB時允許編碼器輸入信號幅度的變化范圍。PCM編解碼器的動態范圍應大於圖9-6所示的CCITT建議框架(樣板值)。
當編碼器輸入信號幅度超過其動態范圍時,出現過載雜訊,故編碼輸入信號幅度過大時量化信噪比急劇下降。TP3057編解碼系統不過載輸入信號的最大幅度為5VP-P。
由於採用對數壓擴技術,PCM編解碼系統可以改善小信號的量化信噪比,TP3057採用A律13折線對信號進行壓擴。當信號處於某一段落時,量化雜訊不變(因在此段落內對信號進行均勻量化),因此在同一段落內量化信噪比隨信號幅度減小而下降。13折線壓擴特性曲線將正負信號各分為8段,第1段信號最小,第8段信號最大。當信號處於第一、二段時,量化雜訊不隨信號幅度變化,因此當信號太小時,量化信噪比會小於25dB,這就是動態范圍的下限。TP3057編解碼系統動態范圍內的輸入信號最小幅度約為0.025Vp-p。
常用1KHz的正弦信號作為輸入信號來測量PCM編解碼器的動態范圍。
圖9-6 PCM編解碼系統動態范圍樣板值
語音信號的抽樣信號頻率為8KHz,為了不發生頻譜混疊,常將語音信號經截止頻率為3.4KHz的低通濾波器處理後再進行A/D處理。語音信號的最低頻率一般為300Hz。TP3057編碼器的低通濾波器和高通濾波器決定了編解碼系統的頻率特性,當輸入信號頻率超過這兩個濾波器的頻率范圍時,解碼輸出信號幅度迅速下降。這就是PCM編解碼系統頻率特性的含義。
四、實驗步驟
1. 熟悉PCM編解碼單元工作原理,開關K9接通8KHz(置為1000狀態),開關K8置為SL1(或SL5、SL7),開關K5、K6分別置於STA-S、STB-S端,接通實驗箱電源。
2. 用示波器觀察STA、STB,調節電位器R19(對應STA)、R20(對應STB),使正弦信號STA、STB波形不失真(峰峰值小於5V)。
3. 用示波器觀察PCM編碼輸出信號。
示波器CH1接SL0,(調整示波器掃描周期以顯示至少兩個SL0脈沖,從而可以觀察完整的一幀信號)CH2分別接SLA、PCM-A、SLB、PCM-B以及PCM,觀察編碼後的數據所處時隙位置與時隙同步信號的關系以及PCM信號的幀結構(注意:本實驗的幀結構中有29個時隙是空時隙,SL0、SLA及SLB的脈沖寬度等於一個時隙寬度)。
開關K8分別接通SL1、SL2、SL5、SL7,觀察PCM基群幀結構的變化情況。
4. 用示波器觀察PCM解碼輸出信號
示波器的CH1接STA,CH2接SRA,觀察這兩個信號波形是否相同(有相位差)。
5. 用示波器定性觀察PCM編解碼器的動態范圍。
開關K5置於STA-IN端,將低失真低頻信號發生器輸出的1KHz正弦信號從STA-IN輸入到TP3057(U82)編碼器。示波器的CH1接STA(編碼輸入),CH2接SRA(解碼輸出)。將信號幅度分別調至大於5VP-P、等於5VP-P,觀察過載和滿載時的解碼輸出波形。再將信號幅度分別衰減10dB、20dB、30dB、40dB、45dB、50dB,觀察解碼輸出波形(當衰減45dB以上時,解碼輸出信號波形上疊加有較明顯的雜訊)。
也可以用本模塊上的正弦信號源來觀察PCM編解碼系統的過載雜訊(只要將STA-S或STB-S信號幅度調至5VP-P以上即可),但必須用專門的信號源才能較方便地觀察到動態范圍。
❸ Swift語言@特性
Swift語言有各種各樣缺乏(或沒有)文檔記錄的特性(attribute)放在那裡等著被使用。讓我們一起看看其中的一些特性:
@inline
這個特性為編譯器提供了內聯提示。有效的取值是__always和never。除非我認為必須要用這兩個值,否則就不會使用它(特別是__always)。到目前為止與其相關的規則還不是很明確,在有限的測試下,它可以正常地工作,但還要視具體情況而定。
進一步的解釋:盡管底層虛擬機(Low Level Virtual Machine, LLVM)有強制內聯的概念,但我們目前還不知道這個@inline特性是否與其直接映射,也不知道是否存在大小方面的限制,但這將會導致編譯器忽略這一點而跳過內聯。理論上說應該是這樣的,但我不保證一定是。
注意(當優化設置關閉時)在調試模式下的構建將忽略@inline。
@transparent
我最初並未將這個特性列出來。該特性會導致編譯器在管道(pipeline)中更早地將函數內聯。它用於 「像+(Int, Int)這樣非常原始的函數」,而「不應該用於獨立函數」 。
甚至在沒有優化設置的調試模式下@transparent特性函數就會被內聯,所以在調用「1+1」這樣的函數的時候並不會特別慢。另外這個特性與@inline(__always)非常類似。
@availability
這個特性可以用來標識某些函數只在某些平台或版本上可用。第一個參數是平台,可以用星號(*)代表一切可用,還可以是iOS或OS X。因為如果需要針對不同的平台,就要指定多個@availability屬性。
如果需要表示該函數在某個給定的平台完全不可用時,可以將第二個參數置為unavailable。此外,還可以用introced,deprecated和obsoleted來指定一個或是多個版本的組合:obsoleted意味著該項已經刪除,deprecated僅僅表示如果使用就會給予警告。最後你可以設置message的值,如果該項被使用了就由編譯器輸出。下面是一些例子:
@noreturn
正如該特性所描述的那樣:編譯器可以假定這個函數是一個永遠循環運行的起點,例如while true { },或者假定是函數abort或者exit進程的情況。
評論者Marco Masser指出,如果調用另一個被標志為@noreturn的函數,那麼編譯器會忽略掉當前函數中缺失的返回值(missing return values),因為編譯器理解程序的控制流。
@asmname
該屬性給出了函數、方法或屬性實現的符號名稱。如果你已經知道對應的函數參數及其類型,那麼就可以直接調用Swift的內部標准庫函數,甚至不用頭文件,也可以方便地調用C語言編寫的函數:
@unsafe_no_objc_tagged_pointer
上面這個仍然是個謎,但我猜測它是在告訴Swift與Objective-C聯系的時候不要使用 tagged pointer 。
@semantics
這又是另一個謎。參數看起來像是array.mutate_unknown或array.init這樣的字元串數組。想必這是要告訴編譯器(或靜態分析器)函數是如何工作的。
@discardableResult
swift正常的方法如果有返回值的話,調用的時候必須有一個接收方,否則的話編譯器會報一個警告,如果在方法前加上 @discardableResult 不處理的時候就不會有警告了。也可以用一個通配符接收方法返回值,可以達到同樣的目的
結論
誰還需要乏味老套的@objc和@autoclosure呢?還是算了吧!
❹ 了解什麼叫做jit compiling,與傳統的編譯技術有何不同
java 應用程序的性能經常成為開發社區中的討論熱點。因為該語言的設計初衷是使用解釋的方式支持應用程序的可移植性目標,早期
Java 運行時所提供的性能級別遠低於 C 和
C++
之類的編譯語言。盡管這些語言可以提供更高的性能,但是生成的代碼只能在有限的幾種系統上執行。在過去的十年中,Java
運行時供應商開發了一些復雜的動態編譯器,通常稱作即時(Just-in-time,JIT)編譯器。程序運行時,JIT
編譯器選擇將最頻繁執行的方法編譯成本地代碼。運行時才進行本地代碼編譯而不是在程序運行前進行編譯(用 C 或
C++ 編寫的程序正好屬於後一情形),保證了可移植性的需求。有些 JIT 編譯器甚至不使用解釋程序就能編譯所有的代碼,但是這些編譯器仍然通過在程序執行時進行一些操作來保持 Java 應用程序的可移植性。
由於動態編譯技術的多項改進,在很多應用程序中,現代的 JIT 編譯器可以產生與 C 或 C++
靜態編譯相當的應用程序性能。但是,仍然有很多軟體開發人員認為 —— 基於經驗或者傳聞 ——
動態編譯可能嚴重干擾程序操作,因為編譯器必須與應用程序共享 CPU。一些開發人員強烈呼籲對 Java
代碼進行靜態編譯,並且堅信那樣可以解決性能問題。對於某些應用程序和執行環境而言,這種觀點是正確的,靜態編譯可以極大地提高 Java
性能,或者說它是惟一的實用選擇。但是,靜態地編譯 Java 應用程序在獲得高性能的同時也帶來了很多復雜性。一般的
Java 開發人員可能並沒有充分地感受到 JIT 動態編譯器的優點。
本文考察了 Java 語言靜態編譯和動態編譯所涉及的一些問題,重點介紹了實時 (RT) 系統。簡要描述了 Java
語言解釋程序的操作原理並說明了現代 JIT 編譯器執行本地代碼編譯的優缺點。介紹了 IBM 在 WebSphere Real Time 中發布的
AOT 編譯技術和它的一些優缺點。然後比較了這兩種編譯策略並指出了幾種比較適合使用 AOT
編譯的應用程序領域和執行環境。要點在於這兩種編譯技術並不互斥:即使在使用這兩種技術最為有效的各種應用程序中,它們也分別存在一些影響應用程序的優缺
點。
執行 Java 程序
Java 程序最初是通過 Java SDK 的 javac程序編譯成本地的與平台無關的格式(類文件)。可將此格式看作 Java
平台,因為它定義了執行 Java 程序所需的所有信息。Java 程序執行引擎,也稱作 Java 運行時環境(JRE),包含了為特定的本地平台實現
Java 平台的虛擬機。例如,基於 Linux 的 Intel x86 平台、Sun Solaris 平台和 AIX 操作系統上運行的 IBM
System p 平台,每個平台都擁有一個 JRE。這些 JRE 實現實現了所有的本地支持,從而可以正確執行為
Java 平台編寫的程序。
事實上,操作數堆棧的大小有實際限制,但是編程人員極少編寫超出該限制的方法。JVM 提供了安全性檢查,對那些創建出此類方法的編程人員進行通知。
Java 平台程序表示的一個重要部分是位元組碼序列,它描述了 Java
類中每個方法所執行的操作。位元組碼使用一個理論上無限大的操作數堆棧來描述計算。這個基於堆棧的程序表示提供了平台無關性,因為它不依賴任何特定本地平台
的 CPU 中可用寄存器的數目。可在操作數堆棧上執行的操作的定義都獨立於所有本地處理器的指令集。Java
虛擬機(JVM)規范定義了這些位元組碼的執行(參見 參考資料)。執行 Java 程序時,用於任何特定本地平台的任何 JRE 都必須遵守 JVM
規范中列出的規則。
因為基於堆棧的本地平台很少(Intel X87 浮點數協處理器是一個明顯的例外),所以大多數本地平台不能直接執行 Java 位元組碼。為了解決這個問題,早期的 JRE 通過解釋位元組碼來執行 Java 程序。即 JVM 在一個循環中重復操作:
◆獲取待執行的下一個位元組碼;
◆解碼;
◆從操作數堆棧獲取所需的操作數;
◆按照 JVM 規范執行操作;
◆將結果寫回堆棧。
這種方法的優點是其簡單性:JRE 開發人員只需編寫代碼來處理每種位元組碼即可。並且因為用於描述操作的位元組碼少於 255 個,所以實現的成本比較低。當然,缺點是性能:這是一個早期造成很多人對 Java 平台不滿的問題,盡管擁有很多其他優點。
解決與 C 或 C++ 之類的語言之間的性能差距意味著,使用不會犧牲可移植性的方式開發用於 Java 平台的本地代碼編譯。
編譯 Java 代碼
盡管傳聞中 Java 編程的 「一次編寫,隨處運行」
的口號可能並非在所有情況下都嚴格成立,但是對於大量的應用程序來說情況確實如此。另一方面,本地編譯本質上是特定於平台的。那麼 Java
平台如何在不犧牲平台無關性的情況下實現本地編譯的性能?答案就是使用 JIT 編譯器進行動態編譯,這種方法已經使用了十年(參見圖 1):
圖 1. JIT 編譯器
使用 JIT 編譯器時,Java
程序按每次編譯一個方法的形式進行編譯,因為它們在本地處理器指令中執行以獲得更高的性能。此過程將生成方法的一個內部表示,該表示與位元組碼不同但是其級
別要高於目標處理器的本地指令。(IBM JIT
編譯器使用一個表達式樹序列表示方法的操作。)編譯器執行一系列優化以提高質量和效率,最後執行一個代碼生成步驟將優化後的內部表示轉換成目標處理器的本
地指令。生成的代碼依賴運行時環境來執行一些活動,比如確保類型轉換的合法性或者對不能在代碼中直接執行的某些類型的對象進行分配。JIT
編譯器操作的編譯線程與應用程序線程是分開的,因此應用程序不需要等待編譯的執行。
圖 1 中還描述了用於觀察執行程序行為的分析框架,通過周期性地對線程取樣找出頻繁執行的方法。該框架還為專門進行分析的方法提供了工具,用來存儲程序的此次執行中可能不會改變的動態值。
因為這個 JIT 編譯過程在程序執行時發生,所以能夠保持平台無關性:發布的仍然是中立的 Java 平台代碼。C 和 C++ 之類的語言缺乏這種優點,因為它們在程序執行前進行本地編譯;發布給(本地平台)執行環境的是本地代碼。
挑戰
盡管通過 JIT 編譯保持了平台無關性,但是付出了一定代價。因為在程序執行時進行編譯,所以編譯代碼的時間將計入程序的執行時間。任何編寫過大型 C 或 C++ 程序的人都知道,編譯過程往往較慢。
為了克服這個缺點,現代的 JIT
編譯器使用了下面兩種方法的任意一種(某些情況下同時使用了這兩種方法)。第一種方法是:編譯所有的代碼,但是不執行任何耗時多的分析和轉換,因此可以快
速生成代碼。由於生成代碼的速度很快,因此盡管可以明顯觀察到編譯帶來的開銷,但是這很容易就被反復執行本地代碼所帶來的性能改善所掩蓋。第二種方法是:
將編譯資源只分配給少量的頻繁執行的方法(通常稱作熱方法)。低編譯開銷更容易被反復執行熱代碼帶來的性能優勢掩蓋。很多應用程序只執行少量的熱方法,因
此這種方法有效地實現了編譯性能成本的最小化。
動態編譯器的一個主要的復雜性在於權衡了解編譯代碼的預期獲益使方法的執行對整個程序的性能起多大作用。一個極端的例子是,程序執行後,您非常清楚哪些方
法對於這個特定的執行的性能貢獻最大,但是編譯這些方法毫無用處,因為程序已經完成。而在另一個極端,程序執行前無法得知哪些方法重要,但是每種方法的潛
在受益都最大化了。大多數動態編譯器的操作介於這兩個極端之間,方法是權衡了解方法預期獲益的重要程度。
Java 語言需要動態載入類這一事實對 Java
編譯器的設計有著重要的影響。如果待編譯代碼引用的其他類還沒有載入怎麼辦?比如一個方法需要讀取某個尚未載入的類的靜態欄位值。Java
語言要求第一次執行類引用時載入這個類並將其解析到當前的 JVM
中。直到第一次執行時才解析引用,這意味著沒有地址可供從中載入該靜態欄位。編譯器如何處理這種可能性?編譯器生成一些代碼,用於在沒有載入類時載入並解
析類。類一旦被解析,就會以一種線程安全的方式修改原始代碼位置以便直接訪問靜態欄位的地址,因為此時已獲知該地址。
IBM JIT
編譯器中進行了大量的努力以便使用安全而有效率的代碼補丁技術,因此在解析類之後,執行的本地代碼只載入欄位的值,就像編譯時已經解析了欄位一樣。另外一
種方法是生成一些代碼,用於在查明欄位的位置以前一直檢查是否已經解析欄位,然後載入該值。對於那些由未解析變成已解析並被頻繁訪問的欄位來說,這種簡單
的過程可能帶來嚴重的性能問題。
動態編譯的優點
動態地編譯 Java 程序有一些重要的優點,甚至能夠比靜態編譯語言更好地生成代碼,現代的 JIT 編譯器常常向生成的代碼中插入掛鉤以收集有關程序行為的信息,以便如果要選擇方法進行重編譯,就可以更好地優化動態行為。
關於此方法的一個很好的例子是收集一個特定 array操作的長度。如果發現每次執行操作時該長度基本不變,則可以為最頻繁使用的
array長度生成專門的代碼,或者可以調用調整為該長度的代碼序列。由於內存系統和指令集設計的特性,用於復制內存的最佳通用常式的執行速度通
常比用於復制特定長度的代碼慢。例如,復制 8
個位元組的對齊的數據可能需要一到兩條指令直接復制,相比之下,使用可以處理任意位元組數和任意對齊方式的一般復制循環可能需要 10 條指令來復制同樣的 8
個位元組。但是,即使此類專門的代碼是為某個特定的長度生成的,生成的代碼也必須正確地執行其他長度的復制。生成代碼只是為了使常見長度的操作執行得更快,
因此平均下來,性能得到了改進。此類優化對大多數靜態編譯語言通常不實用,因為所有可能的執行中長度恆定的操作比一個特定程序執行中長度恆定的操作要少得
多。
此類優化的另一個重要的例子是基於類層次結構的優化。例如,一個虛方法調用需要查看接收方對象的類調用,以便找出哪個實際目標實現了接收方對象的虛方法。
研究表明:大多數虛調用只有一個目標對應於所有的接收方對象,而 JIT
編譯器可以為直接調用生成比虛調用更有效率的代碼。通過分析代碼編譯後類層次結構的狀態,JIT
編譯器可以為虛調用找到一個目標方法,並且生成直接調用目標方法的代碼而不是執行較慢的虛調用。當然,如果類層次結構發生變化,並且出現另外的目標方法,
則 JIT
編譯器可以更正最初生成的代碼以便執行虛調用。在實踐中,很少需要作出這些更正。另外,由於可能需要作出此類更正,因此靜態地執行這種優化非常麻煩。
因為動態編譯器通常只是集中編譯少量的熱方法,所以可以執行更主動的分析來生成更好的代碼,使編譯的回報更高。事實上,大部分現代的
JIT
編譯器也支持重編譯被認為是熱方法的方法。可以使用靜態編譯器(不太強調編譯時間)中常見的非常主動的優化來分析和轉換這些頻繁執行的方法,以便生成更好
的代碼並獲得更高的性能。
這些改進及其他一些類似的改進所產生的綜合效果是:對於大量的 Java 應用程序來說,動態編譯已經彌補了與 C 和 C++ 之類語言的靜態本地編譯性能之間的差距,在某些情況下,甚至超過了後者的性能。
缺點
但是,動態編譯確實具有一些缺點,這些缺點使它在某些情況下算不上一個理想的解決方案。例如,因為識別頻繁執行的方法以及編譯這些方法需要時間,所以應用
程序通常要經歷一個准備過程,在這個過程中性能無法達到其最高值。在這個准備過程中出現性能問題有幾個原因。首先,大量的初始編譯可能直接影響應用程序的
啟動時間。不僅這些編譯延遲了應用程序達到穩定狀態的時間(想像 Web
伺服器經
歷一個初始階段後才能夠執行實際有用的工作),而且在准備階段中頻繁執行的方法可能對應用程序的穩定狀態的性能所起的作用也不大。如果 JIT
編譯會延遲啟動又不能顯著改善應用程序的長期性能,則執行這種編譯就非常浪費。雖然所有的現代 JVM
都執行調優來減輕啟動延遲,但是並非在所有情況下都能夠完全解決這個問題。
其次,有些應用程序完全不能忍受動態編譯帶來的延遲。如 GUI 介面之類互動式應用程序就是這樣的例子。在這種情況下,編譯活動可能對用戶使用造成不利影響,同時又不能顯著地改善應用程序的性能。
最後,用於實時環境並具有嚴格的任務時限的應用程序可能無法忍受編譯的不確定性性能影響或動態編譯器本身的內存開銷。
因此,雖然 JIT 編譯技術已經能夠提供與靜態語言性能相當(甚至更好)的性能水平,但是動態編譯並不適合於某些應用程序。在這些情況下,Java 代碼的提前(Ahead-of-time,AOT)編譯可能是合適的解決方案。
AOT Java 編譯
大致說來,Java 語言本地編譯應該是為傳統語言(如 C++ 或
Fortran)而開發的編譯技術的一個簡單應用。不幸的是,Java 語言本身的動態特性帶來了額外的復雜性,影響了 Java
程序靜態編譯代碼的質量。但是基本思想仍然是相同的:在程序執行前生成 Java 方法的本地代碼,以便在程序運行時直接使用本地代碼。目的在於避免
JIT 編譯器的運行時性能消耗或內存消耗,或者避免解釋程序的早期性能開銷。
挑戰
動態類載入是動態 JIT 編譯器面臨的一個挑戰,也是 AOT
編譯的一個更重要的問題。只有在執行代碼引用類的時候才載入該類。因為是在程序執行前進行 AOT
編譯的,所以編譯器無法預測載入了哪些類。就是說編譯器無法獲知任何靜態欄位的地址、任何對象的任何實例欄位的偏移量或任何調用的實際目標,甚至對直接調
用(非虛調用)也是如此。在執行代碼時,如果證明對任何這類信息的預測是錯誤的,這意味著代碼是錯誤的並且還犧牲了 Java 的一致性。
因為代碼可以在任何環境中執行,所以類文件可能與代碼編譯時不同。例如,一個 JVM
實例可能從磁碟的某個特定位置載入類,而後面一個實例可能從不同的位置甚至網路載入該類。設想一個正在進行 bug
修復的開發環境:類文件的內容可能隨不同的應用程序的執行而變化。此外,Java 代碼可能在程序執行前根本不存在:比如 Java
反射服務通常在運行時生成新類來支持程序的行為。
缺少關於靜態、欄位、類和方法的信息意味著嚴重限制了 Java 編譯器中優化框架的大部分功能。內聯可能是靜態或動態編譯器應用的最重要的優化,但是由於編譯器無法獲知調用的目標方法,因此無法再使用這種優化。
內聯
內聯是一種用於在運行時生成代碼避免程序開始和結束時開銷的技術,方法是將函數的調用代碼插入到調用方的函數中。但是內聯最大的益處可能是優化方可見的代碼的范圍擴大了,從而能夠生成更高質量的代碼。下面是一個內聯前的代碼示例:
int foo() { int x=2, y=3; return bar(x,y); }final int bar(int a, int b) { return a+b; }
如果編譯器可以證明這個 bar就是 foo()中調用的那個方法,則 bar中的代碼可以取代 foo()中對
bar()的調用。這時,bar()方法是 final類型,因此肯定是 foo()中調用的那個方法。甚至在一些虛調用例子中,動態 JIT
編譯器通常能夠推測性地內聯目標方法的代碼,並且在絕大多數情況下能夠正確使用。編譯器將生成以下代碼:
int foo() { int x=2, y=3; return x+y; }
在這個例子中,簡化前名為值傳播的優化可以生成直接返回
5的代碼。如果不使用內聯,則不能執行這種優化,產生的性能就會低很多。如果沒有解析
bar()方法(例如靜態編譯),則不能執行這種優化,而代碼必須執行虛調用。運行時,實際調用的可能是另外一個執行兩個數字相乘而不是相加的
bar方法。所以不能在 Java 程序的靜態編譯期間直接使用內聯。
AOT
代碼因此必須在沒有解析每個靜態、欄位、類和方法引用的情況下生成。執行時,每個這些引用必須利用當前運行時環境的正確值進行更新。這個過程可能直接影響
第一次執行的性能,因為在第一次執行時將解析所有引用。當然,後續執行將從修補代碼中獲益,從而可以更直接地引用實例、靜態欄位或方法目標。
另外,為 Java 方法生成的本地代碼通常需要使用僅在單個 JVM 實例中使用的值。例如,代碼必須調用 JVM
運行時中的某些運行時常式來執行特定操作,如查找未解析的方法或分配內存。這些運行時常式的地址可能在每次將 JVM 載入到內存時變化。因此 AOT
編譯代碼需要綁定到 JVM 的當前執行環境中,然後才能執行。其他的例子有字元串的地址和常量池入口的內部位置。
在 WebSphere Real Time 中,AOT 本地代碼編譯通過 jxeinajar工具(參見圖 2)來執行。該工具對 JAR 文件中所有類的所有方法應用本地代碼編譯,也可以選擇性地對需要的方法應用本地代碼編譯。結果被存儲到名為 Java eXEcutable (JXE) 的內部格式中,但是也可輕松地存儲到任意的持久性容器中。
您可能認為對所有的代碼進行靜態編譯是最好的方法,因為可以在運行時執行最大數量的本地代碼。但是此處可以作出一些權衡。編譯的方法越多,代碼佔用的內存
就越多。編譯後的本地代碼大概比位元組碼大 10 倍:本地代碼本身的密度比位元組碼小,而且必須包含代碼的附加元數據,以便將代碼綁定到 JVM
中,並且在出現異常或請求堆棧跟蹤時正確執行代碼。構成普通 Java 應用程序的 JAR
文件通常包含許多很少執行的方法。編譯這些方法會消耗內存卻沒有什麼預期收益。相關的內存消耗包括以下過程:將代碼存儲到磁碟上、從磁碟取出代碼並裝入
JVM,以及將代碼綁定到 JVM。除非多次執行代碼,否則這些代價不能由本地代碼相對解釋的性能優勢來彌補。
圖 2. jxeinajar
跟大小問題相違背的一個事實是:在編譯過的方法和解釋過的方法之間進行的調用(即編譯過的方法調用解釋過的方法,或者相反)可能比這兩類方法各自內部之間
進行的調用所需的開銷大。動態編譯器通過最終編譯所有由 JIT
編譯代碼頻繁調用的那些解釋過的方法來減少這項開銷,但是如果不使用動態編譯器,則這項開銷就不可避免。因此如果是選擇性地編譯方法,則必須謹慎操作以使
從已編譯方法到未編譯方法的轉換最小化。為了在所有可能的執行中都避免這個問題而選擇正確的方法會非常困難。
優點
雖然 AOT 編譯代碼具有上述的缺點和挑戰,但是提前編譯 Java 程序可以提高性能,尤其是在不能將動態編譯器作為有效解決方案的環境中。
可以通過謹慎地使用 AOT 編譯代碼加快應用程序啟動,因為雖然這種代碼通常比 JIT
編譯代碼慢,但是卻比解釋代碼快很多倍。此外,因為載入和綁定 AOT
編譯代碼的時間通常比檢測和動態編譯一個重要方法的時間少,所以能夠在程序執行的早期達到那樣的性能。類似地,互動式應用程序可以很快地從本地代碼中獲
益,無需使用引起較差響應能力的動態編譯。
RT 應用程序也能從 AOT 編譯代碼中獲得重要的收益:更具確定性的性能超過了解釋的性能。WebSphere Real Time
使用的動態 JIT 編譯器針對在 RT 系統中的使用進行了專門的調整。使編譯線程以低於 RT
任務的優先順序操作,並且作出了調整以避免生成帶有嚴重的不確定性性能影響的代碼。但是,在一些 RT 環境中,出現 JIT
編譯器是不可接受的。此類環境通常需要最嚴格的時限管理控制。在這些例子中,AOT
編譯代碼可以提供比解釋過的代碼更好的原始性能,又不會影響現有的確定性。消除 JIT
編譯線程甚至消除了啟動更高優先順序 RT 任務時發生的線程搶占所帶來的性能影響。
優缺點統計
動態(JIT)編譯器支持平台中立性,並通過利用應用程序執行的動態行為和關於載入的類及其層次結構的信息來生成高質量的代碼。但是
JIT
編譯器具有一個有限的編譯時預算,而且會影響程序的運行時性能。另一方面,靜態(AOT)編譯器則犧牲了平台無關性和代碼質量,因為它們不能利用程序的動
態行為,也不具有關於載入的類或類層次結構的信息。AOT 編譯擁有有效無限制的編譯時預算,因為 AOT
編譯時間不會影響運行時性能,但是在實踐中開發人員不會長期等待靜態編譯步驟的完成。
表 1 總結了本文討論的 Java 語言動態和靜態編譯器的一些特性:
表 1. 比較編譯技術
兩種技術都需要謹慎選擇編譯的方法以實現最高的性能。對動態編譯器而言,編譯器自身作出決策,而對於靜態編譯器,由開發人員作出選擇。讓
JIT 編譯器選擇編譯的方法是不是優點很難說,取決於編譯器在給定情形中推斷能力的好壞。在大多數情況下,我們認為這是一種優點。
因為它們可以最好地優化運行中的程序,所以 JIT 編譯器在提供穩定狀態性能方面更勝一籌,而這一點在大量的生產 Java
系統中最為重要。靜態編譯可以產生最佳的互動式性能,因為沒有運行時編譯行為來影響用戶預期的響應時間。通過調整動態編譯器可以在某種程度上解決啟動和確
定性性能問題,但是靜態編譯在需要時可提供最快的啟動速度和最高級別的確定性。表 2 在四種不同的執行環境中對這兩種編譯技術進行了比較:
表 2. 使用這些技術的最佳環境
圖 3 展示了啟動性能和穩定狀態性能的總體趨勢:
圖 3. AOT 和 JIT 的性能對比
使用 JIT 編譯器的初始階段性能很低,因為要首先解釋方法。隨著編譯方法的增多及 JIT
執行編譯所需時間的縮短,性能曲線逐漸升高最後達到性能峰值。另一方面,AOT 編譯代碼啟動時的性能比解釋的性能高很多,但是無法達到 JIT
編譯器所能達到的最高性能。將靜態代碼綁定到 JVM 實例中會產生一些開銷,因此開始時的性能比穩定狀態的性能值低,但是能夠比使用 JIT
編譯器更快地達到穩定狀態的性能水平。
沒有一種本地代碼編譯技術能夠適合所有的 Java
執行環境。某種技術所擅長的通常正是其他技術的弱項。出於這個原因,需要同時使用這兩種編譯技術以滿足 Java
應用程序開發人員的要求。事實上,可以結合使用靜態和動態編譯以便提供最大可能的性能提升 —— 但是必須具備平台無關性,它是 Java
語言的主要賣點,因此不成問題。
結束語
本文探討了 Java 語言本地代碼編譯的問題,主要介紹了 JIT 編譯器形式的動態編譯和靜態 AOT 編譯,比較了二者的優缺點。
雖然動態編譯器在過去的十年裡實現了極大的成熟,使大量的各種 Java 應用程序可以趕上或超過靜態編譯語言(如 C++ 或
Fortran)所能夠達到的性能。但是動態編譯在某些類型的應用程序和執行環境中仍然不太合適。雖然 AOT
編譯號稱動態編譯缺點的萬能解決方案,但是由於 Java 語言本身的動態特性,它也面臨著提供本地編譯全部潛能的挑戰。
這兩種技術都不能解決 Java 執行環境中本地代碼編譯的所有需求,但是反過來又可以在最有效的地方作為工具使用。這兩種技術可以相互補充。能夠恰當地使用這兩種編譯模型的運行時系統可以使很大范圍內的應用程序開發環境中的開發人員和用戶受益。
❺ C語言中使用inline函數會降低cache命中率么
inline vs. __forceinline
MS Visual C++, 以及其它幾種編譯器,提供了一個非標準的用於控制函數內嵌(inline)的關鍵字,作為對標准關鍵字inline的補充。為什麼要添加這個非標准關鍵字呢?先讓我們來看看inline的一些局限,決定一個聲明為inline的函數是否真的進行嵌入,完全取決於編譯器的判斷。因此inline只是一個建議,在一些情況下,比如在一些內嵌函數中包含有循環或是這個函數體太大了,那麼即使這個函數聲明為inline,編譯器也將拒絕這個函數的嵌入。
與此相反,非標准關鍵字__forceinline 將忽略編譯器的判斷並強迫編譯器去嵌入一個它本該拒絕嵌入的函數。我不太肯定使用這個關鍵字的意義,它可能會使可執行文件變得臃腫並降低cache的命中率。幸運的是,在一些極端條件下,編譯器可能不接受__forceinline的任何請求。所以,一般情況下最好是使用標準的inline,inline是可移植的並且讓編譯器去做出「正確的選擇」。
__forceinline 只應在下列條件全為真的情況下使用:inline不被編譯器接受;你的代碼不需要向其它平台進行移植;並且你能肯定嵌入這個函數會提高性能。
❻ 如何減少C++編寫程序的CPU使用率
優化是一個非常大的主題,本文並不是去深入探討性能分析理論,演算法的效率,況且我也沒有這個能力。我只是想把一些可以簡單的應用到你的C++代碼中的優化技術總結在這里,這樣,當你遇到幾種不同的編程策略的時候,就可以對每種策略的性能進行一個大概的估計。這也是本文的目的之所在。
一. 優化之前
在進行優化之前,我們首先應該做的是發現我們代碼的瓶頸(bottleneck)在哪裡。然而當你做這件事情的時候切忌從一個debug- version進行推斷,因為debug-version中包含了許多額外的代碼。一個debug-version可執行體要比release- version大出40%。那些額外的代碼都是用來支持調試的,比如說符號的查找。大多數實現都為debug-version和release- version提供了不同的operator new以及庫函數。而且,一個release-version的執行體可能已經通過多種途徑進行了優化,包括不必要的臨時對象的消除,循環展開,把對象移入寄存器,內聯等等。
另外,我們要把調試和優化區分開來,它們是在完成不同的任務。 debug-version 是用來追捕bugs以及檢查程序是否有邏輯上的問題。release-version則是用來做一些性能上的調整以及進行優化。
下面就讓我們來看看有哪些代碼優化技術吧:
二. 聲明的放置
程序中變數和對象的聲明放在什麼位置將會對性能產生顯著影響。同樣,對postfix和prefix運算符的選擇也會影響性能。這一部分我們集中討論四個問題:初始化v.s 賦值,在程序確實要使用的地方放置聲明,構造函數的初始化列表,prefix v.s postfix運算符。
(1)請使用初始化而不是賦值
在C語言中只允許在一個函數體的開頭進行變數的聲明,然而在C++中聲明可以出現在程序的任何位置。這樣做的目的是希望把對象的聲明拖延到確實要使用它的時候再進行。這樣做可以有兩個好處:1. 確保了對象在它被使用前不會被程序的其他部分惡意修改。如果對象在開頭就被聲明然而卻在20行以後才被使用的話,就不能做這樣的保證。2. 使我們有機會通過用初始化取代賦值來達到性能的提升,從前聲明只能放在開頭,然而往往開始的時候我們還沒有獲得我們想要的值,因此初始化所帶來的好處就無法被應用。但是現在我們可以在我們獲得了想要的值的時候直接進行初始化,從而省去了一步。注意,或許對於基本類型來說,初始化和賦值之間可能不會有什麼差異,但是對於用戶定義的類型來說,二者就會帶來顯著的不同,因為賦值會多進行一次函數調用----operator =。因此當我們在賦值和初始化之間進行選擇的話,初始化應該是我們的首選。
(2)把聲明放在合適的位置上
在一些場合,通過移動聲明到合適的位置所帶來的性能提升應該引起我們足夠的重視。例如:
bool is_C_Needed();
void use()
{
C c1;
if (is_C_Needed() == false)
{
return; //c1 was not needed
}
//use c1 here
return;
}
上面這段代碼中對象c1即使在有可能不使用它的情況下也會被創建,這樣我們就會為它付出不必要的花費,有可能你會說一個對象c1能浪費多少時間,但是如果是這種情況呢:C c1[1000];我想就不是說浪費就浪費了。但是我們可以通過移動聲明c1的位置來改變這種情況:
void use()
{
if (is_C_Needed() == false)
{
return; //c1 was not needed
}
C c1; //moved from the block's beginning
//use c1 here
return;
}
怎麼樣,程序的性能是不是已經得到很大的改善了呢?因此請仔細分析你的代碼,把聲明放在合適的位置上,它所帶來的好處是你難以想像的。
(3) 初始化列表
我們都知道,初始化列表一般是用來初始化const或者reference數據成員。但是由於他自身的性質,我們可以通過使用初始化列表來實現性能的提升。我們先來看一段程序:
class Person
{
private:
C c_1;
C c_2;
public:
Person(const C& c1, const C& c2 ): c_1(c1), c_2(c2) {}
};
當然構造函數我們也可以這樣寫:
Person::Person(const C& c1, const C& c2)
{
c_1 = c1;
c_2 = c2;
}
那麼究竟二者會帶來什麼樣的性能差異呢,要想搞清楚這個問題,我們首先要搞清楚二者是如何執行的,先來看初始化列表:數據成員的聲明操作都是在構造函數執行之前就完成了,在構造函數中往往完成的只是賦值操作,然而初始化列表直接是在數據成員聲明的時候就進行了初始化,因此它只執行了一次 constructor。再來看在構造函數中賦值的情況:首先,在構造函數執行前會通過default constructor創建數據成員,然後在構造函數中通過operator =進行賦值。因此它就比初始化列表多進行了一次函數調用。性能差異就出來了。但是請注意,如果你的數據成員都是基本類型的話,那麼為了程序的可讀性就不要使用初始化列表了,因為編譯器對兩者產生的匯編代碼是相同的。
(4)postfix VS prefix 運算符
prefix運算符++和—比它的postfix版本效率更高,因為當postfix運算符被使用的時候,會需要一個臨時對象來保存改變以前的值。對於基本類型,編譯器會消除這一份額外的拷貝,但是對於用戶定義類型,這似乎是不可能的。因此請你盡可能使用prefix運算符
三. 內聯函數
內聯函數既能夠去除函數調用所帶來的效率負擔又能夠保留一般函數的優點。然而,內聯函數並不是萬能葯,在一些情況下,它甚至能夠降低程序的性能。因此在使用的時候應該慎重。
1.我們先來看看內聯函數給我們帶來的好處:從一個用戶的角度來看,內聯函數看起來和普通函數一樣,它可以有參數和返回值,也可以有自己的作用域,然而它卻不會引入一般函數調用所帶來的負擔。另外,它可以比宏更安全更容易調試。
當然有一點應該意識到,inline specifier僅僅是對編譯器的建議,編譯器有權利忽略這個建議。那麼編譯器是如何決定函數內聯與否呢?一般情況下關鍵性因素包括函數體的大小,是否有局部對象被聲明,函數的復雜性等等。
2.那麼如果一個函數被聲明為inline但是卻沒有被內聯將會發生什麼呢?理論上,當編譯器拒絕內聯一個函數的時候,那個函數會像普通函數一樣被對待,但是還會出現一些其他的問題。例如下面這段代碼:
// filename Time.h
#include
#include
using namespace std;
class Time
{
public:
inline void Show() { for (int i = 0; i<10; i++) cout< };
因為成員函數Time::Show()包括一個局部變數和一個for循環,所以編譯器一般拒絕inline,並且把它當作一個普通的成員函數。但是這個包含類聲明的頭文件會被單獨的#include進各個獨立的編譯單元中:
// filename f1.cpp
#include "Time.hj"
void f1()
{
Time t1;
t1.Show();
}
// filename f2.cpp
#include "Time.h"
void f2()
{
Time t2;
t2.Show();
}
結果編譯器為這個程序生成了兩個相同成員函數的拷貝:
void f1();
void f2();
int main()
{
f1();
f2();
return 0;
}
當程序被鏈接的時候,linker將會面對兩個相同的Time::Show()拷貝,於是函數重定義的連接錯誤發生。但是老一些的C++實現對付這種情況的辦法是通過把一個un-inlined函數當作static來處理。因此每一份函數拷貝僅僅在自己的編譯單元中可見,這樣鏈接錯誤就解決了,但是在程序中卻會留下多份函數拷貝。在這種情況下,程序的性能不但沒有提升,反而增加了編譯和鏈接時間以及最終可執行體的大小。
但是幸運的是,新的C++標准中關於un-inlined函數的說法已經改變。一個符合標准C++實現應該只生成一份函數拷貝。然而,要想所有的編譯器都支持這一點可能還需要很長時間。
另外關於內聯函數還有兩個更令人頭疼的問題。第一個問題是該如何進行維護。一個函數開始的時候可能以內聯的形式出現,但是隨著系統的擴展,函數體可能要求添加額外的功能,結果內聯函數就變得不太可能,因此需要把inline specifier去除以及把函數體放到一個單獨的源文件中。另一個問題是當內聯函數被應用在代碼庫的時候產生。當內聯函數改變的時候,用戶必須重新編譯他們的代碼以反映這種改變。然而對於一個非內聯函數,用戶僅僅需要重新鏈接就可以了。
這里想要說的是,內聯函數並不是一個增強性能的靈丹妙葯。只有當函數非常短小的時候它才能得到我們想要的效果,但是如果函數並不是很短而且在很多地方都被調用的話,那麼將會使得可執行體的體積增大。最令人煩惱的還是當編譯器拒絕內聯的時候。在老的實現中,結果很不盡人意,雖然在新的實現中有很大的改善,但是仍然還是不那麼完善的。一些編譯器能夠足夠的聰明來指出哪些函數可以內聯哪些不能,但是,大多數編譯器就不那麼聰明了,因此這就需要我們的經驗來判斷。如果內聯函數不能增強行能,就避免使用它.
