自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

帶你了解五種加速Go的特性和如何實現(xiàn)它們

開發(fā) 后端
我最近被邀請在 Gocon 發(fā)表演講,這是一個每半年在日本東京舉行的 Go 的精彩大會。Gocon 2014 是一個完全由社區(qū)驅(qū)動的為期一天的活動,由培訓和一整個下午的圍繞著生產(chǎn)環(huán)境中的 Go 這個主題的演講組成。

Anthony Starks 使用他出色的 Deck 演示工具重構(gòu)了我原來的基于 Google Slides 的幻燈片。你可以在他的博客上查看他重構(gòu)后的幻燈片, 

mindchunk.blogspot.com.au/2014/06/remixing-with-deck。

 

我最近被邀請在 Gocon 發(fā)表演講,這是一個每半年在日本東京舉行的 Go 的精彩大會。Gocon 2014 是一個完全由社區(qū)驅(qū)動的為期一天的活動,由培訓和一整個下午的圍繞著生產(chǎn)環(huán)境中的 Go 這個主題的演講組成。(LCTT 譯注:本文發(fā)表于 2014 年)

以下是我的講義。原文的結(jié)構(gòu)能讓我緩慢而清晰的演講,因此我已經(jīng)編輯了它使其更可讀。

我要感謝 Bill Kennedy 和 Minux Ma,特別是 Josh Bleecher Snyder,感謝他們在我準備這次演講中的幫助。

大家下午好。

我叫 David.

我很高興今天能來到 Gocon。我想?yún)⒓舆@個會議已經(jīng)兩年了,我很感謝主辦方能提供給我向你們演講的機會。

Gocon 2014

Gocon 2014

我想以一個問題開始我的演講。

為什么選擇 Go?

當大家討論學習或在生產(chǎn)環(huán)境中使用 Go 的原因時,答案不一而足,但因為以下三個原因的最多。

Gocon 2014

Gocon 2014

這就是 TOP3 的原因。

***,并發(fā)。

Go 的 并發(fā)原語Concurrency Primitives 對于來自 Nodejs,Ruby 或 Python 等單線程腳本語言的程序員,或者來自 C++ 或 Java 等重量級線程模型的語言都很有吸引力。

易于部署。

我們今天從經(jīng)驗豐富的 Gophers 那里聽說過,他們非常欣賞部署 Go 應(yīng)用的簡單性。

Gocon 2014

Gocon 2014

然后是性能。

我相信人們選擇 Go 的一個重要原因是它 。

Gocon 2014 (4)

Gocon 2014 (4)

在今天的演講中,我想討論五個有助于提高 Go 性能的特性。

我還將與大家分享 Go 如何實現(xiàn)這些特性的細節(jié)。

Gocon 2014 (5)

Gocon 2014 (5)

我要談的***個特性是 Go 對于值的高效處理和存儲。

Gocon 2014 (6)

Gocon 2014 (6)

這是 Go 中一個值的例子。編譯時,gocon 正好消耗四個字節(jié)的內(nèi)存。

讓我們將 Go 與其他一些語言進行比較

Gocon 2014 (7)

Gocon 2014 (7)

由于 Python 表示變量的方式的開銷,使用 Python 存儲相同的值會消耗六倍的內(nèi)存。

Python 使用額外的內(nèi)存來跟蹤類型信息,進行 引用計數(shù)Reference Counting 等。

讓我們看另一個例子:

Gocon 2014 (8)

Gocon 2014 (8)

與 Go 類似,Java 消耗 4 個字節(jié)的內(nèi)存來存儲 int 型。

但是,要在像 List 或 Map 這樣的集合中使用此值,編譯器必須將其轉(zhuǎn)換為 Integer 對象。

Gocon 2014 (9)

Gocon 2014 (9)

因此,Java 中的整數(shù)通常消耗 16 到 24 個字節(jié)的內(nèi)存。

為什么這很重要? 內(nèi)存便宜且充足,為什么這個開銷很重要?

Gocon 2014 (10)

Gocon 2014 (10)

這是一張顯示 CPU 時鐘速度與內(nèi)存總線速度的圖表。

請注意 CPU 時鐘速度和內(nèi)存總線速度之間的差距如何繼續(xù)擴大。

兩者之間的差異實際上是 CPU 花費多少時間等待內(nèi)存。

Gocon 2014 (11)

Gocon 2014 (11)

自 1960 年代后期以來,CPU 設(shè)計師已經(jīng)意識到了這個問題。

他們的解決方案是一個緩存,一個更小、更快的內(nèi)存區(qū)域,介入 CPU 和主存之間。

Gocon 2014 (12)

Gocon 2014 (12)

這是一個 Location 類型,它保存物體在三維空間中的位置。它是用 Go 編寫的,因此每個 Location 只消耗 24 個字節(jié)的存儲空間。

我們可以使用這種類型來構(gòu)造一個容納 1000 個 Location 的數(shù)組類型,它只消耗 24000 字節(jié)的內(nèi)存。

在數(shù)組內(nèi)部,Location 結(jié)構(gòu)體是順序存儲的,而不是隨機存儲的 1000 個 Location 結(jié)構(gòu)體的指針。

這很重要,因為現(xiàn)在所有 1000 個 Location 結(jié)構(gòu)體都按順序放在緩存中,緊密排列在一起。

Gocon 2014 (13)

Gocon 2014 (13)

Go 允許您創(chuàng)建緊湊的數(shù)據(jù)結(jié)構(gòu),避免不必要的填充字節(jié)。

緊湊的數(shù)據(jù)結(jié)構(gòu)能更好地利用緩存。

更好的緩存利用率可帶來更好的性能。

Gocon 2014 (14)

Gocon 2014 (14)

函數(shù)調(diào)用不是無開銷的。

Gocon 2014 (15)

Gocon 2014 (15)

調(diào)用函數(shù)時會發(fā)生三件事。

創(chuàng)建一個新的 棧幀Stack Frame,并記錄調(diào)用者的詳細信息。

在函數(shù)調(diào)用期間可能被覆蓋的任何寄存器都將保存到棧中。

處理器計算函數(shù)的地址并執(zhí)行到該新地址的分支。

Gocon 2014 (16)

Gocon 2014 (16)

由于函數(shù)調(diào)用是非常常見的操作,因此 CPU 設(shè)計師一直在努力優(yōu)化此過程,但他們無法消除開銷。

函調(diào)固有開銷,或重于泰山,或輕于鴻毛,這取決于函數(shù)做了什么。

減少函數(shù)調(diào)用開銷的解決方案是 內(nèi)聯(lián)Inlining

Gocon 2014 (17)

Gocon 2014 (17)

Go 編譯器通過將函數(shù)體視為調(diào)用者的一部分來內(nèi)聯(lián)函數(shù)。

內(nèi)聯(lián)也有成本,它增加了二進制文件大小。

只有當調(diào)用開銷與函數(shù)所做工作關(guān)聯(lián)度的很大時內(nèi)聯(lián)才有意義,因此只有簡單的函數(shù)才能用于內(nèi)聯(lián)。

復雜的函數(shù)通常不受調(diào)用它們的開銷所支配,因此不會內(nèi)聯(lián)。

Gocon 2014 (18)

Gocon 2014 (18)

這個例子顯示函數(shù) Double 調(diào)用 util.Max。

為了減少調(diào)用 util.Max 的開銷,編譯器可以將 util.Max 內(nèi)聯(lián)到 Double 中,就象這樣

Gocon 2014 (19)

Gocon 2014 (19)

內(nèi)聯(lián)后不再調(diào)用 util.Max,但是 Double 的行為沒有改變。

內(nèi)聯(lián)并不是 Go 獨有的。幾乎每種編譯或及時編譯的語言都執(zhí)行此優(yōu)化。但是 Go 的內(nèi)聯(lián)是如何實現(xiàn)的?

Go 實現(xiàn)非常簡單。編譯包時,會標記任何適合內(nèi)聯(lián)的小函數(shù),然后照常編譯。

然后函數(shù)的源代碼和編譯后版本都會被存儲。

Gocon 2014 (20)

Gocon 2014 (20)

此幻燈片顯示了 util.a 的內(nèi)容。源代碼已經(jīng)過一些轉(zhuǎn)換,以便編譯器更容易快速處理。

當編譯器編譯 Double 時,它看到 util.Max 可內(nèi)聯(lián)的,并且 util.Max 的源代碼是可用的。

就會替換原函數(shù)中的代碼,而不是插入對 util.Max 的編譯版本的調(diào)用。

擁有該函數(shù)的源代碼可以實現(xiàn)其他優(yōu)化。

Gocon 2014 (21)

Gocon 2014 (21)

在這個例子中,盡管函數(shù) Test 總是返回 false,但 Expensive 在不執(zhí)行它的情況下無法知道結(jié)果。

當 Test 被內(nèi)聯(lián)時,我們得到這樣的東西。

Gocon 2014 (22)

Gocon 2014 (22)

編譯器現(xiàn)在知道 Expensive 的代碼無法訪問。

這不僅節(jié)省了調(diào)用 Test 的成本,還節(jié)省了編譯或運行任何現(xiàn)在無法訪問的 Expensive 代碼。

Go 編譯器可以跨文件甚至跨包自動內(nèi)聯(lián)函數(shù)。還包括從標準庫調(diào)用的可內(nèi)聯(lián)函數(shù)的代碼。

Gocon 2014 (23)

Gocon 2014 (23)

強制垃圾回收Mandatory Garbage Collection 使 Go 成為一種更簡單,更安全的語言。

這并不意味著垃圾回收會使 Go 變慢,或者垃圾回收是程序速度的瓶頸。

這意味著在堆上分配的內(nèi)存是有代價的。每次 GC 運行時都會花費 CPU 時間,直到釋放內(nèi)存為止。

Gocon 2014 (24)

Gocon 2014 (24)

然而,有另一個地方分配內(nèi)存,那就是棧。

與 C 不同,它強制您選擇是否將值通過 malloc 將其存儲在堆上,還是通過在函數(shù)范圍內(nèi)聲明將其儲存在棧上;Go 實現(xiàn)了一個名為 逃逸分析Escape Analysis 的優(yōu)化。

Gocon 2014 (25)

Gocon 2014 (25)

逃逸分析決定了對一個值的任何引用是否會從被聲明的函數(shù)中逃逸。

如果沒有引用逃逸,則該值可以安全地存儲在棧中。

存儲在棧中的值不需要分配或釋放。

讓我們看一些例子

Gocon 2014 (26)

Gocon 2014 (26)

Sum 返回 1 到 100 的整數(shù)的和。這是一種相當不尋常的做法,但它說明了逃逸分析的工作原理。

因為切片 numbers 僅在 Sum 內(nèi)引用,所以編譯器將安排到棧上來存儲的 100 個整數(shù),而不是安排到堆上。

沒有必要回收 numbers,它會在 Sum 返回時自動釋放。

Gocon 2014 (27)

Gocon 2014 (27)

第二個例子也有點尬。在 CenterCursor 中,我們創(chuàng)建一個新的 Cursor 對象并在 c 中存儲指向它的指針。

然后我們將 c 傳遞給 Center() 函數(shù),它將 Cursor 移動到屏幕的中心。

***我們打印出那個 ‘Cursor` 的 X 和 Y 坐標。

即使 c 被 new 函數(shù)分配了空間,它也不會存儲在堆上,因為沒有引用 c 的變量逃逸 CenterCursor 函數(shù)。

Gocon 2014 (28)

Gocon 2014 (28)

默認情況下,Go 的優(yōu)化始終處于啟用狀態(tài)。可以使用 -gcflags = -m 開關(guān)查看編譯器的逃逸分析和內(nèi)聯(lián)決策。

因為逃逸分析是在編譯時執(zhí)行的,而不是運行時,所以無論垃圾回收的效率如何,棧分配總是比堆分配快。

我將在本演講的其余部分詳細討論棧。

Gocon 2014 (29)

Gocon 2014 (29)

Go 有 goroutine。 這是 Go 并發(fā)的基石。

我想退一步,探索 goroutine 的歷史。

最初,計算機一次運行一個進程。在 60 年代,多進程或 分時Time Sharing 的想法變得流行起來。

在分時系統(tǒng)中,操作系統(tǒng)必須通過保護當前進程的現(xiàn)場,然后恢復另一個進程的現(xiàn)場,不斷地在這些進程之間切換 CPU 的注意力。

這稱為 進程切換。

Gocon 2014 (30)

Gocon 2014 (30)

進程切換有三個主要開銷。

首先,內(nèi)核需要保護該進程的所有 CPU 寄存器的現(xiàn)場,然后恢復另一個進程的現(xiàn)場。

內(nèi)核還需要將 CPU 的映射從虛擬內(nèi)存刷新到物理內(nèi)存,因為這些映射僅對當前進程有效。

***是操作系統(tǒng) 上下文切換Context Switch 的成本,以及 調(diào)度函數(shù)Scheduler Function 選擇占用 CPU 的下一個進程的開銷。

Gocon 2014 (31)

Gocon 2014 (31)

現(xiàn)代處理器中有數(shù)量驚人的寄存器。我很難在一張幻燈片上排開它們,這可以讓你知道保護和恢復它們需要多少時間。

由于進程切換可以在進程執(zhí)行的任何時刻發(fā)生,因此操作系統(tǒng)需要存儲所有寄存器的內(nèi)容,因為它不知道當前正在使用哪些寄存器。

Gocon 2014 (32)

Gocon 2014 (32)

這導致了線程的出生,這些線程在概念上與進程相同,但共享相同的內(nèi)存空間。

由于線程共享地址空間,因此它們比進程更輕,因此創(chuàng)建速度更快,切換速度更快。

Gocon 2014 (33)

Gocon 2014 (33)

Goroutine 升華了線程的思想。

Goroutine 是 協(xié)作式調(diào)度Cooperative Scheduled
的,而不是依靠內(nèi)核來調(diào)度。

當對 Go 運行時調(diào)度器Runtime Scheduler 進行顯式調(diào)用時,goroutine 之間的切換僅發(fā)生在明確定義的點上。

編譯器知道正在使用的寄存器并自動保存它們。

Gocon 2014 (34)

Gocon 2014 (34)

雖然 goroutine 是協(xié)作式調(diào)度的,但運行時會為你處理。

Goroutine 可能會給禪讓給其他協(xié)程時刻是:

  • 阻塞式通道發(fā)送和接收。
  • Go 聲明,雖然不能保證會立即調(diào)度新的 goroutine。
  • 文件和網(wǎng)絡(luò)操作式的阻塞式系統(tǒng)調(diào)用。
  • 在被垃圾回收循環(huán)停止后。

Gocon 2014 (35)

Gocon 2014 (35)

這個例子說明了上一張幻燈片中描述的一些調(diào)度點。

箭頭所示的線程從左側(cè)的 ReadFile 函數(shù)開始。遇到 os.Open,它在等待文件操作完成時阻塞線程,因此調(diào)度器將線程切換到右側(cè)的 goroutine。

繼續(xù)執(zhí)行直到從通道 c 中讀,并且此時 os.Open 調(diào)用已完成,因此調(diào)度器將線程切換回左側(cè)并繼續(xù)執(zhí)行 file.Read 函數(shù),然后又被文件 IO 阻塞。

調(diào)度器將線程切換回右側(cè)以進行另一個通道操作,該操作在左側(cè)運行期間已解鎖,但在通道發(fā)送時再次阻塞。

***,當 Read 操作完成并且數(shù)據(jù)可用時,線程切換回左側(cè)。

Gocon 2014 (36)

Gocon 2014 (36)

這張幻燈片顯示了低級語言描述的 runtime.Syscall 函數(shù),它是 os 包中所有函數(shù)的基礎(chǔ)。

只要你的代碼調(diào)用操作系統(tǒng),就會通過此函數(shù)。

對 entersyscall 的調(diào)用通知運行時該線程即將阻塞。

這允許運行時啟動一個新線程,該線程將在當前線程被阻塞時為其他 goroutine 提供服務(wù)。

這導致每 Go 進程的操作系統(tǒng)線程相對較少,Go 運行時負責將可運行的 Goroutine 分配給空閑的操作系統(tǒng)線程。

Gocon 2014 (37)

Gocon 2014 (37)

在上一節(jié)中,我討論了 goroutine 如何減少管理許多(有時是數(shù)十萬個并發(fā)執(zhí)行線程)的開銷。

Goroutine故事還有另一面,那就是棧管理,它引導我進入我的***一個話題。

Gocon 2014 (39)

Gocon 2014 (38)

這是一個進程的內(nèi)存布局圖。我們感興趣的關(guān)鍵是堆和棧的位置。

傳統(tǒng)上,在進程的地址空間內(nèi),堆位于內(nèi)存的底部,位于程序(代碼)的上方并向上增長。

棧位于虛擬地址空間的頂部,并向下增長。

Gocon 2014 (40)

Gocon 2014 (39)

因為堆和棧相互覆蓋的結(jié)果會是災(zāi)難性的,操作系統(tǒng)通常會安排在棧和堆之間放置一個不可寫內(nèi)存區(qū)域,以確保如果它們發(fā)生碰撞,程序?qū)⒅兄埂?/p>

這稱為保護頁,有效地限制了進程的棧大小,通常大約為幾兆字節(jié)。

Gocon 2014 (41)

Gocon 2014 (40)

我們已經(jīng)討論過線程共享相同的地址空間,因此對于每個線程,它必須有自己的棧。

由于很難預(yù)測特定線程的棧需求,因此為每個線程的棧和保護頁面保留了大量內(nèi)存。

希望是這些區(qū)域永遠不被使用,而且防護頁永遠不會被擊中。

缺點是隨著程序中線程數(shù)的增加,可用地址空間的數(shù)量會減少。

Gocon 2014 (42)

Gocon 2014 (41)

我們已經(jīng)看到 Go 運行時將大量的 goroutine 調(diào)度到少量線程上,但那些 goroutines 的棧需求呢?

Go 編譯器不使用保護頁,而是在每個函數(shù)調(diào)用時插入一個檢查,以檢查是否有足夠的棧來運行該函數(shù)。如果沒有,運行時可以分配更多的??臻g。

由于這種檢查,goroutines 初始??梢宰龅酶?,這反過來允許 Go 程序員將 goroutines 視為廉價資源。

Gocon 2014 (43)

Gocon 2014 (42)

這是一張顯示了 Go 1.2 如何管理棧的幻燈片。

當 G 調(diào)用 H 時,沒有足夠的空間讓 H 運行,所以運行時從堆中分配一個新的棧幀,然后在新的棧段上運行 H。當 H 返回時,棧區(qū)域返回到堆,然后返回到 G。

Gocon 2014 (44)

Gocon 2014 (43)

這種管理棧的方法通常很好用,但對于某些類型的代碼,通常是遞歸代碼,它可能導致程序的內(nèi)部循環(huán)跨越這些棧邊界之一。

例如,在程序的內(nèi)部循環(huán)中,函數(shù) G 可以在循環(huán)中多次調(diào)用 H,

每次都會導致棧拆分。 這被稱為 熱分裂Hot Split 問題。

Gocon 2014 (45)

Gocon 2014 (44)

為了解決熱分裂問題,Go 1.3 采用了一種新的棧管理方法。

如果 goroutine 的棧太小,則不會添加和刪除其他棧段,而是分配新的更大的棧。

舊棧的內(nèi)容被復制到新棧,然后 goroutine 使用新的更大的棧繼續(xù)運行。

在***次調(diào)用 H 之后,棧將足夠大,對可用??臻g的檢查將始終成功。

這解決了熱分裂問題。

Gocon 2014 (46)

Gocon 2014 (45)

值,內(nèi)聯(lián),逃逸分析,Goroutines 和分段/復制棧。

這些是我今天選擇談?wù)摰奈鍌€特性,但它們絕不是使 Go 成為快速的語言的唯一因素,就像人們引用他們學習 Go 的理由的三個原因一樣。

這五個特性一樣強大,它們不是孤立存在的。

例如,運行時將 goroutine 復用到線程上的方式在沒有可擴展棧的情況下幾乎沒有效率。

內(nèi)聯(lián)通過將較小的函數(shù)組合成較大的函數(shù)來降低棧大小檢查的成本。

逃逸分析通過自動將從實例從堆移動到棧來減少垃圾回收器的壓力。

逃逸分析還提供了更好的 緩存局部性Cache Locality。

如果沒有可增長的棧,逃逸分析可能會對棧施加太大的壓力。

Gocon 2014 (47)

Gocon 2014 (46)

  • 感謝 Gocon 主辦方允許我今天發(fā)言
  • twitter / web / email details
  • 感謝 @offbymany,@billkennedy_go 和 Minux 在準備這個演講的過程中所提供的幫助。

 

 

 

責任編輯:龐桂玉 來源: Linux中國
相關(guān)推薦

2022-02-22 23:39:15

JavaScript編程語言Web

2020-07-12 22:09:38

智能工廠物聯(lián)網(wǎng)基礎(chǔ)設(shè)施安全風險

2022-08-01 10:41:51

談判策略CIO

2018-09-11 09:00:50

工具開發(fā)應(yīng)用程序

2021-10-19 07:27:08

HTTP代理網(wǎng)絡(luò)

2022-02-23 09:36:11

GoRuby編程語言

2021-03-10 08:55:42

Go數(shù)據(jù)語言

2018-03-01 16:25:52

Linux內(nèi)核內(nèi)存管理

2024-08-13 11:13:18

2024-07-15 08:00:00

2021-09-27 07:39:52

Go初始化函數(shù)package

2022-02-02 21:29:39

路由模式Vue-Router

2022-06-09 10:33:46

欺騙工具網(wǎng)絡(luò)攻擊

2023-01-11 10:29:26

2023-04-28 07:49:13

Javawaitsleep

2022-01-17 08:56:05

CSS 技巧代碼重構(gòu)

2019-09-20 08:00:00

開發(fā)技能Web開發(fā) 人工智能

2023-05-16 14:44:07

2020-02-19 19:26:27

K8S開源平臺容器技術(shù)

2020-09-01 15:57:12

云安全云遷移云計算
點贊
收藏

51CTO技術(shù)棧公眾號