成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Go中的內(nèi)聯(lián)優(yōu)化

開發(fā) 后端
本文討論 Go 編譯器是如何實現(xiàn)內(nèi)聯(lián)的,以及這種優(yōu)化方法如何影響你的 Go 代碼。

[[324890]]

本文討論 Go 編譯器是如何實現(xiàn)內(nèi)聯(lián)的,以及這種優(yōu)化方法如何影響你的 Go 代碼。

請注意:本文重點討論 gc,這是來自 golang.org 的事實標準的 Go 編譯器。討論到的概念可以廣泛適用于其它 Go 編譯器,如 gccgo 和 llgo,但它們在實現(xiàn)方式和功效上可能有所差異。

內(nèi)聯(lián)是什么?

內(nèi)聯(lián)inlining就是把簡短的函數(shù)在調(diào)用它的地方展開。在計算機發(fā)展歷程的早期,這個優(yōu)化是由程序員手動實現(xiàn)的。現(xiàn)在,內(nèi)聯(lián)已經(jīng)成為編譯過程中自動實現(xiàn)的基本優(yōu)化過程的其中一步。

為什么內(nèi)聯(lián)很重要?

有兩個原因。第一個是它消除了函數(shù)調(diào)用本身的開銷。第二個是它使得編譯器能更高效地執(zhí)行其他的優(yōu)化策略。

函數(shù)調(diào)用的開銷

在任何語言中,調(diào)用一個函數(shù) 1 都會有消耗。把參數(shù)編組進寄存器或放入棧中(取決于 ABI),在返回結果時的逆反過程都會有開銷。引入一次函數(shù)調(diào)用會導致程序計數(shù)器從指令流的一點跳到另一點,這可能導致管道滯后。函數(shù)內(nèi)部通常有前置處理preamble,需要為函數(shù)執(zhí)行準備新的棧幀,還有與前置相似的后續(xù)處理epilogue,需要在返回給調(diào)用方之前釋放棧幀空間。

在 Go 中函數(shù)調(diào)用會消耗額外的資源來支持棧的動態(tài)增長。在進入函數(shù)時,goroutine 可用的??臻g與函數(shù)需要的空間大小進行比較。如果可用空間不同,前置處理就會跳到運行時runtime的邏輯中,通過把數(shù)據(jù)復制到一塊新的、更大的空間的來增長??臻g。當這個復制完成后,運行時就會跳回到原來的函數(shù)入口,再執(zhí)行??臻g檢查,現(xiàn)在通過了檢查,函數(shù)調(diào)用繼續(xù)執(zhí)行。這種方式下,goroutine 開始時可以申請很小的??臻g,在有需要時再申請更大的空間。2

這個檢查消耗很小,只有幾個指令,而且由于 goroutine 的棧是成幾何級數(shù)增長的,因此這個檢查很少失敗。這樣,現(xiàn)代處理器的分支預測單元可以通過假定檢查肯定會成功來隱藏??臻g檢查的消耗。當處理器預測錯了棧空間檢查,不得不放棄它在推測性執(zhí)行所做的操作時,與為了增加 goroutine 的棧空間運行時所需的操作消耗的資源相比,管道滯后的代價更小。

雖然現(xiàn)代處理器可以用預測性執(zhí)行技術優(yōu)化每次函數(shù)調(diào)用中的泛型和 Go 特定的元素的開銷,但那些開銷不能被完全消除,因此在每次函數(shù)調(diào)用執(zhí)行必要的工作過程中都會有性能消耗。一次函數(shù)調(diào)用本身的開銷是固定的,與更大的函數(shù)相比,調(diào)用小函數(shù)的代價更大,因為在每次調(diào)用過程中它們做的有用的工作更少。

因此,消除這些開銷的方法必須是要消除函數(shù)調(diào)用本身,Go 的編譯器就是這么做的,在某些條件下通過用函數(shù)的內(nèi)容來替換函數(shù)調(diào)用來實現(xiàn)。這個過程被稱為內(nèi)聯(lián),因為它在函數(shù)調(diào)用處把函數(shù)體展開了。

改進的優(yōu)化機會

Cliff Click 博士把內(nèi)聯(lián)描述為現(xiàn)代編譯器做的優(yōu)化措施,像常量傳播(LCTT 譯注:此處作者筆誤,原文為 constant proportion,修正為 constant propagation)和死代碼消除一樣,都是編譯器的基本優(yōu)化方法。實際上,內(nèi)聯(lián)可以讓編譯器看得更深,使編譯器可以觀察調(diào)用的特定函數(shù)的上下文內(nèi)容,可以看到能繼續(xù)簡化或徹底消除的邏輯。由于可以遞歸地執(zhí)行內(nèi)聯(lián),因此不僅可以在每個獨立的函數(shù)上下文處進行這種優(yōu)化決策,也可以在整個函數(shù)調(diào)用鏈中進行。

實踐中的內(nèi)聯(lián)

下面這個例子可以演示內(nèi)聯(lián)的影響:

  1. package main
  2.  
  3. import "testing"
  4.  
  5. //go:noinline
  6. func max(a, b int) int {
  7. if a > b {
  8. return a
  9. }
  10. return b
  11. }
  12.  
  13. var Result int
  14.  
  15. func BenchmarkMax(b *testing.B) {
  16. var r int
  17. for i := 0; i < b.N; i++ {
  18. r = max(-1, i)
  19. }
  20. Result = r
  21. }

運行這個基準,會得到如下結果:3

  1. % go test -bench=.
  2. BenchmarkMax-4 530687617 2.24 ns/op

在我的 2015 MacBook Air 上 max(-1, i) 的耗時約為 2.24 納秒?,F(xiàn)在去掉 //go:noinline 編譯指令,再看下結果:

  1. % go test -bench=.
  2. BenchmarkMax-4 1000000000 0.514 ns/op

從 2.24 納秒降到了 0.51 納秒,或者從 benchstat 的結果可以看出,有 78% 的提升。

  1. % benchstat {old,new}.txt
  2. name old time/op new time/op delta
  3. Max-4 2.21ns ± 1% 0.49ns ± 6% -77.96% (p=0.000 n=18+19)

這個提升是從哪兒來的呢?

首先,移除掉函數(shù)調(diào)用以及與之關聯(lián)的前置處理 4 是主要因素。把 max 函數(shù)的函數(shù)體在調(diào)用處展開,減少了處理器執(zhí)行的指令數(shù)量并且消除了一些分支。

現(xiàn)在由于編譯器優(yōu)化了 BenchmarkMax,因此它可以看到 max 函數(shù)的內(nèi)容,進而可以做更多的提升。當 max 被內(nèi)聯(lián)后,BenchmarkMax 呈現(xiàn)給編譯器的樣子,看起來是這樣的:

  1. func BenchmarkMax(b *testing.B) {
  2. var r int
  3. for i := 0; i < b.N; i++ {
  4. if -1 > i {
  5. r = -1
  6. } else {
  7. r = i
  8. }
  9. }
  10. Result = r
  11. }

再運行一次基準,我們看一下手動內(nèi)聯(lián)的版本和編譯器內(nèi)聯(lián)的版本的表現(xiàn):

  1. % benchstat {old,new}.txt
  2. name old time/op new time/op delta
  3. Max-4 2.21ns ± 1% 0.48ns ± 3% -78.14% (p=0.000 n=18+18)

現(xiàn)在編譯器能看到在 BenchmarkMax 里內(nèi)聯(lián) max 的結果,可以執(zhí)行以前不能執(zhí)行的優(yōu)化措施。例如,編譯器注意到 i 初始值為 0,僅做自增操作,因此所有與 i 的比較都可以假定 i 不是負值。這樣條件表達式 -1 > i 永遠不是 true。5

證明了 -1 > i 永遠不為 true 后,編譯器可以把代碼簡化為:

  1. func BenchmarkMax(b *testing.B) {
  2. var r int
  3. for i := 0; i < b.N; i++ {
  4. if false {
  5. r = -1
  6. } else {
  7. r = i
  8. }
  9. }
  10. Result = r
  11. }

并且因為分支里是個常量,編譯器可以通過下面的方式移除不會走到的分支:

  1. func BenchmarkMax(b *testing.B) {
  2. var r int
  3. for i := 0; i < b.N; i++ {
  4. r = i
  5. }
  6. Result = r
  7. }

這樣,通過內(nèi)聯(lián)和由內(nèi)聯(lián)解鎖的優(yōu)化過程,編譯器把表達式 r = max(-1, i)) 簡化為 r = i。

內(nèi)聯(lián)的限制

本文中我論述的內(nèi)聯(lián)稱作葉子內(nèi)聯(lián)leaf inlining:把函數(shù)調(diào)用棧中最底層的函數(shù)在調(diào)用它的函數(shù)處展開的行為。內(nèi)聯(lián)是個遞歸的過程,當把函數(shù)內(nèi)聯(lián)到調(diào)用它的函數(shù) A 處后,編譯器會把內(nèi)聯(lián)后的結果代碼再內(nèi)聯(lián)到 A 的調(diào)用方,這樣持續(xù)內(nèi)聯(lián)下去。例如,下面的代碼:

  1. func BenchmarkMaxMaxMax(b *testing.B) {
  2. var r int
  3. for i := 0; i < b.N; i++ {
  4. r = max(max(-1, i), max(0, i))
  5. }
  6. Result = r
  7. }

與之前的例子中的代碼運行速度一樣快,因為編譯器可以對上面的代碼重復地進行內(nèi)聯(lián),也把代碼簡化到 r = i 表達式。

下一篇文章中,我會論述當 Go 編譯器想要內(nèi)聯(lián)函數(shù)調(diào)用棧中間的某個函數(shù)時選用的另一種內(nèi)聯(lián)策略。最后我會論述編譯器為了內(nèi)聯(lián)代碼準備好要達到的極限,這個極限 Go 現(xiàn)在的能力還達不到。 

責任編輯:龐桂玉 來源: Linux中國
相關推薦

2020-05-06 20:40:03

Go編程語言

2023-11-21 08:03:43

語言架構偏移量

2010-03-22 14:58:35

2021-08-13 09:06:52

Go高性能優(yōu)化

2010-09-08 17:11:29

CSS塊元素CSS內(nèi)聯(lián)元素

2021-06-08 07:45:44

Go語言優(yōu)化

2023-11-20 09:57:03

內(nèi)聯(lián)函數(shù)C++

2023-09-12 11:10:00

代碼優(yōu)化Go

2023-12-30 18:35:37

Go識別應用程序

2021-08-04 09:33:22

Go 性能優(yōu)化

2023-05-09 11:02:22

Go內(nèi)聯(lián)版本

2024-01-15 11:12:28

Go內(nèi)存開發(fā)

2011-06-21 11:05:41

內(nèi)聯(lián)函數(shù)

2025-05-22 09:01:28

2022-07-19 08:01:55

函數(shù)Go格式化

2022-02-14 08:04:02

Go語法糖編譯器

2016-09-22 09:37:14

GCC內(nèi)聯(lián)語法

2024-12-04 11:31:41

Go編程技巧

2021-09-01 07:21:46

堆棧Gopanic

2022-06-13 07:03:25

Go 語言怎么優(yōu)化重
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 欧美色综合网 | 欧美一区二区视频 | 久草视频网站 | 日韩播放 | 精品乱人伦一区二区三区 | 欧美午夜在线 | 欧美99 | 极品粉嫩国产48尤物在线播放 | 色.com| 一级毛片视频 | 欧美久久久久久久 | 午夜在线| 91亚洲国产成人精品一区二三 | 国产精品久久久久久婷婷天堂 | 成人3d动漫一区二区三区91 | 亚洲成人久久久 | 国产中文视频 | 久久久久国产一区二区三区不卡 | 99久久国产 | 久在线视频 | 亚洲国产精品一区二区www | 欧美亚洲视频 | 天天干天天操天天看 | 亚洲成人av在线播放 | 99热精品在线观看 | 日日骚网 | 国产精品18久久久久久白浆动漫 | 亚洲综合色婷婷 | 久久久久久国产精品mv | 亚洲综合小视频 | 日韩在线视频一区二区三区 | 日韩欧美在线视频观看 | 国产精品日韩欧美一区二区 | 天天拍天天操 | 韩日精品视频 | 久久这里只有精品首页 | 国产伦精品一区二区三区视频金莲 | 欧美一区二区三区在线观看视频 | 欧美xxxx在线| 久久久久久久久久久久久久国产 | 亚洲人成人一区二区在线观看 |