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

Go中的內聯優化

開發 后端
本文討論 Go 編譯器是如何實現內聯的,以及這種優化方法如何影響你的 Go 代碼。

[[324890]]

本文討論 Go 編譯器是如何實現內聯的,以及這種優化方法如何影響你的 Go 代碼。

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

內聯是什么?

內聯inlining就是把簡短的函數在調用它的地方展開。在計算機發展歷程的早期,這個優化是由程序員手動實現的。現在,內聯已經成為編譯過程中自動實現的基本優化過程的其中一步。

為什么內聯很重要?

有兩個原因。第一個是它消除了函數調用本身的開銷。第二個是它使得編譯器能更高效地執行其他的優化策略。

函數調用的開銷

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

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

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

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

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

改進的優化機會

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

實踐中的內聯

下面這個例子可以演示內聯的影響:

  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 納秒。現在去掉 //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)

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

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

現在由于編譯器優化了 BenchmarkMax,因此它可以看到 max 函數的內容,進而可以做更多的提升。當 max 被內聯后,BenchmarkMax 呈現給編譯器的樣子,看起來是這樣的:

  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. }

再運行一次基準,我們看一下手動內聯的版本和編譯器內聯的版本的表現:

  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)

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

證明了 -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. }

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

內聯的限制

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

  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. }

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

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

責任編輯:龐桂玉 來源: 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高性能優化

2010-09-08 17:11:29

CSS塊元素CSS內聯元素

2021-06-08 07:45:44

Go語言優化

2023-11-20 09:57:03

內聯函數C++

2023-09-12 11:10:00

代碼優化Go

2023-12-30 18:35:37

Go識別應用程序

2021-08-04 09:33:22

Go 性能優化

2023-05-09 11:02:22

Go內聯版本

2011-06-21 11:05:41

內聯函數

2024-01-15 11:12:28

Go內存開發

2016-09-22 09:37:14

GCC內聯語法

2022-02-14 08:04:02

Go語法糖編譯器

2022-07-19 08:01:55

函數Go格式化

2025-05-22 09:01:28

2021-09-01 07:21:46

堆棧Gopanic

2024-12-04 11:31:41

Go編程技巧

2022-06-13 07:03:25

Go 語言怎么優化重
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 九九成人 | 欧美在线亚洲 | 一区在线视频 | av天天澡天天爽天天av | 超碰91在线 | 欧美黑人狂野猛交老妇 | 日韩在线视频免费观看 | 天天操综合网 | av手机在线播放 | 在线成人免费视频 | 人人艹人人 | 国产美女永久免费无遮挡 | 午夜影院在线观看免费 | 欧美 日韩 国产 在线 | 欧美日韩不卡合集视频 | 精品一区二区免费视频 | 久久99精品视频 | 亚洲欧美日韩精品久久亚洲区 | 人人干人人玩 | 欧美精品日韩精品 | 亚洲 日本 欧美 中文幕 | 亚洲视频www | 欧美日韩精品一区 | 黄网站涩免费蜜桃网站 | 国产三级大片 | 国产1区2区 | 亚洲精品小视频在线观看 | 天天干夜夜操视频 | 久久久久久九九九九 | 天天射视频 | 欧美色综合一区二区三区 | 亚洲成在线观看 | 91中文| 亚洲 欧美 激情 另类 校园 | 久久久www成人免费无遮挡大片 | 中文字幕乱码一区二区三区 | 天天操天天摸天天干 | 国产精品久久久久一区二区三区 | 婷婷综合激情 | 日本欧美大片 | 欧美激情啪啪 |