亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

公告:魔扣目錄網(wǎng)為廣大站長提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.430618.com 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

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

 

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

  • 來源:https://linux.cn/article-12176-1.html
  • 作者:Dave Cheney
  • 譯者:Xiaobin.Liu

 

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

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

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

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

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

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

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

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

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

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

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

改進(jìn)的優(yōu)化機(jī)會

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

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

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

package main

import "testing"

//go:noinline
func max(a, b int) int {
    if a > b {
        return a
    }
    return b
}

var Result int

func BenchmarkMax(b *testing.B) {
    var r int
    for i := 0; i < b.N; i++ {
        r = max(-1, i)
    }
    Result = r
}

運(yùn)行這個基準(zhǔn),會得到如下結(jié)果: 3

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

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

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

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

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

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

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

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

func BenchmarkMax(b *testing.B) {
    var r int
    for i := 0; i < b.N; i++ {
        if -1 > i {
            r = -1
        } else {
            r = i
        }
    }
    Result = r
}

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

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

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

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

func BenchmarkMax(b *testing.B) {
    var r int
    for i := 0; i < b.N; i++ {
        if false {
            r = -1
        } else {
            r = i
        }
    }
    Result = r
}

并且因?yàn)榉种Ю锸莻€常量,編譯器可以通過下面的方式移除不會走到的分支:

func BenchmarkMax(b *testing.B) {
    var r int
    for i := 0; i < b.N; i++ {
        r = i
    }
    Result = r
}

這樣,通過內(nèi)聯(lián)和由內(nèi)聯(lián)解鎖的優(yōu)化過程,編譯器把表達(dá)式 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)是個遞歸的過程,當(dāng)把函數(shù)內(nèi)聯(lián)到調(diào)用它的函數(shù) A 處后,編譯器會把內(nèi)聯(lián)后的結(jié)果代碼再內(nèi)聯(lián)到 A 的調(diào)用方,這樣持續(xù)內(nèi)聯(lián)下去。例如,下面的代碼:

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

與之前的例子中的代碼運(yùn)行速度一樣快,因?yàn)榫幾g器可以對上面的代碼重復(fù)地進(jìn)行內(nèi)聯(lián),也把代碼簡化到 r = i 表達(dá)式。

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

相關(guān)文章:

  1. 使 Go 變快的 5 件事
  2. 為什么 Goroutine 的棧空間會無限增長?
  3. Go 中怎么寫基準(zhǔn)測試
  4. Go 中隱藏的編譯指令

  1. 在 Go 中,一個方法就是一個有預(yù)先定義的形參和接受者的函數(shù)。假設(shè)這個方法不是通過接口調(diào)用的,調(diào)用一個無消耗的函數(shù)所消耗的代價與引入一個方法是相同的。 ?
  2. 在 Go 1.14 以前,棧檢查的前置處理也被垃圾回收器用于 STW,通過把所有活躍的 goroutine 棧空間設(shè)為 0,來強(qiáng)制它們切換為下一次函數(shù)調(diào)用時的運(yùn)行時狀態(tài)。這個機(jī)制 最近被替換 為一種新機(jī)制,新機(jī)制下運(yùn)行時可以不用等 goroutine 進(jìn)行函數(shù)調(diào)用就可以暫停 goroutine。 ?
  3. 我用 //go:noinline 編譯指令來阻止編譯器內(nèi)聯(lián) max。這是因?yàn)槲蚁氚褍?nèi)聯(lián) max 的影響與其他影響隔離開,而不是用 -gcflags='-l -N' 選項(xiàng)在全局范圍內(nèi)禁止優(yōu)化。關(guān)于 //go: 注釋在 這篇文章 中詳細(xì)論述。 ?
  4. 你可以自己通過比較 go test -bench=. -gcflags=-S 有無 //go:noinline 注釋時的不同結(jié)果來驗(yàn)證一下。 ?
  5. 你可以用 -gcflags=-d=ssa/prove/debug=on 選項(xiàng)來自己驗(yàn)證一下。 ?

via: https://dave.cheney.net/2020/04/25/inlining-optimisations-in-go

作者: Dave Cheney 選題: lujun9972 譯者: lxbwolf 校對: wxy

本文由 LCTT 原創(chuàng)編譯, Linux中國 榮譽(yù)推出

分享到:
標(biāo)簽:內(nèi)聯(lián) 優(yōu)化
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運(yùn)動步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績評定2018-06-03

通用課目體育訓(xùn)練成績評定