手機(jī)端網(wǎng)站制作seo推廣有哪些
參考:?
Go 匯編函數(shù) - Go 語言高級編程
Go 嵌套匯編 - 掘金 (juejin.cn)
前言:
Golang 適用 Go-Runtime(Go 運(yùn)行時(shí),嵌入在被編譯的PE可執(zhí)行文件之中)來管理調(diào)度協(xié)同程式的運(yùn)行。
Go 語言沒有多線程(MT)的概念,在 Go 語言之中,每個(gè) Go 協(xié)程就類似開辟了一個(gè)新的線程,效率上,肯定是比分配線程好的。
但也僅限于分配協(xié)程,及單個(gè)進(jìn)程可以跑幾萬個(gè)乃至幾十萬個(gè)協(xié)同程序,這是線程無法比擬的,因?yàn)樵诓僮飨到y(tǒng)之中,最小執(zhí)行單元的單位就是線程了,但是線程相對協(xié)同程序來說,過重,無論是內(nèi)存還是CPU。
但不意味著 Go 協(xié)程執(zhí)行的效率比線程要好,別太自信與盲目,協(xié)程是比不了線程代碼CPU執(zhí)行效率的。
上面也提到了,只是可以同時(shí)開辟幾萬個(gè)乃至幾十萬個(gè)協(xié)程,并且啟動協(xié)程速度比線程快非常多,這是它的優(yōu)勢,但是缺點(diǎn)也很明顯,在物理線程上執(zhí)行 Go 協(xié)同程式的代碼效率不高。
目前世界上最快的協(xié)同程序切換,應(yīng)該是 C/C++ 之中的:
State Threads Library (sourceforge.net)
boost::context?
兩個(gè)庫各有千秋,但相對來說 boost 更好用一些,在這里需要提醒大家一點(diǎn),應(yīng)用程序之中運(yùn)行協(xié)同程序,它是依托于進(jìn)程之中的物理線程上執(zhí)行的。
來到正題,我們先來探討 Golang 到底是 “Stackless” 無棧輕量協(xié)程,還是 “Stackful” 有棧重量協(xié)程呢?
那么就有必要分析清楚,有棧協(xié)程跟無棧協(xié)程之間到底有什么區(qū)別。
首先:
1、有棧協(xié)程
? ? ? 1.1、棧協(xié)程是一種基于線程或進(jìn)程的協(xié)程實(shí)現(xiàn)方式。
? ? ? 1.2、棧協(xié)程擁有自己的執(zhí)行棧,可以獨(dú)立地管理?xiàng)?、局部變量和函?shù)調(diào)用。
? ? ? 1.3、棧協(xié)程的切換需要保存和恢復(fù)整個(gè)執(zhí)行上下文,包括棧指針、寄存器等。
? ? ? 1.4、由于棧協(xié)程具有獨(dú)立的執(zhí)行棧,因此它們可以支持遞歸調(diào)用和深度嵌套。
? ? ? 1.5、由于棧協(xié)程需要額外的資源來維護(hù)棧,因此在創(chuàng)建和銷毀方面可能會有一些開銷。?
2、無棧協(xié)程
? ? ? 2.1、無棧協(xié)同是一種基于用戶空間的協(xié)程實(shí)現(xiàn)方式。
? ? ? 2.2、無棧協(xié)同沒有獨(dú)立的執(zhí)行棧,它們共享相同的調(diào)用棧?!局攸c(diǎn)】
? ? ? 2.3、無棧協(xié)同使用狀態(tài)機(jī)來管理協(xié)程的執(zhí)行,并通過保存和恢復(fù)狀態(tài)來實(shí)現(xiàn)協(xié)程的切換。
? ? ? 2.4、由于無棧協(xié)同共享調(diào)用棧,因此它們不能支持遞歸調(diào)用和深度嵌套。
? ? ? 2.5、無棧協(xié)同通常比棧協(xié)程更輕量級,創(chuàng)建和銷毀開銷較小。
似乎從上述定義的概念來說,Golang 是有棧協(xié)議?但真的是這樣嗎?顯然不是的,首先真正意義上的有棧協(xié)程,是無法被運(yùn)行時(shí)代管的。
有棧協(xié)程存在以下幾個(gè)限制:
1、如果開發(fā)人員切換協(xié)程處理不當(dāng)?shù)那闆r下,會導(dǎo)致協(xié)程棧內(nèi)存泄漏問題。
2、如果開發(fā)人員在多個(gè)線程之中執(zhí)行
3、有棧協(xié)程無法動態(tài)擴(kuò)展計(jì)算??臻g,所以有棧協(xié)程需要在分配時(shí),明確指定棧空間大小。
一個(gè)協(xié)同程序可以在多個(gè)線程上按保證順序性(時(shí)序)進(jìn)行處理,無論是有棧協(xié)同程序、或者是無棧協(xié)同程序,均可以。
Go 協(xié)同程序是屬于 “Stackless” 無棧協(xié)程的類型,但 Go 為了實(shí)現(xiàn)協(xié)同程序能像 Stackful 有棧協(xié)程一樣,擁有屬于自己的外掛棧空間,并且支持動態(tài)??臻g擴(kuò)容。
但要注意一點(diǎn):
1、Go 協(xié)程可能在不同的線程上面被執(zhí)行,雖然 Go 語言運(yùn)行時(shí)保證了,單一協(xié)同程序執(zhí)行的時(shí)序性,但開發(fā)人員需要在其中注意協(xié)同程序之間的同步問題,類似多線程并發(fā)編程。
2、若要實(shí)現(xiàn)同步鎖的情況,人們需要考慮多線程問題,否則這可能造成很嚴(yán)重的后果,即 Go 運(yùn)行時(shí)附著的工作線程被阻塞,同時(shí)最好的實(shí)現(xiàn)方式偽同步鎖,如利用管道來實(shí)現(xiàn)類似效果。
相對傳統(tǒng)的 TTASLock/CAS自選鎖實(shí)現(xiàn),可能不太適合Go 這種結(jié)構(gòu)的程序,這是因?yàn)?#xff1a;Go 協(xié)同程序在沒有執(zhí)行異步的情況下是不會讓出線程CPU的,你可以理解為,你需要執(zhí)行類似文件IO、網(wǎng)絡(luò)IO、或者調(diào)用 Go 運(yùn)行時(shí)庫之中的同步庫,例如:sync.Mutex 產(chǎn)生了阻塞行為
鑒于?Go 運(yùn)行時(shí)是多線程執(zhí)行,在不阻塞 Go 運(yùn)行時(shí)最大工作線程的情況下,其它協(xié)程,仍舊是可以正常就緒的工作的,這取決于運(yùn)行時(shí)調(diào)度。
所以嚴(yán)格意義上來說,Go 協(xié)程屬于 “Stackless” + “Stackful” 的變種協(xié)程,它屬于 “Stackless” 無棧協(xié)同程序的一種,但 Go 編譯器實(shí)現(xiàn)對其用戶代碼進(jìn)行展開,并分配一個(gè) “Go 外掛計(jì)算棧內(nèi)存空間單元”,而非真正意義上的函數(shù)棧,如同C#、C++、C#、ASM、IL函數(shù)的調(diào)用堆棧。
有棧協(xié)程無法放大執(zhí)行堆棧的根本原因是寄存器,EIP、RIP,及地址鏈之間存在上下依賴問題等等,Go 并非是真的有棧協(xié)程,自然不會存在這個(gè)問題,它本來就是由編譯器支持的黑魔法,實(shí)現(xiàn)的協(xié)同程序(“重點(diǎn):最終會被展開編譯為狀態(tài)機(jī)切換的”),但這類編譯器不能編譯過度復(fù)雜協(xié)同應(yīng)用程序,雖然我個(gè)人是相信 Google 的技術(shù)水平的,但并不代表,不對 Stackless 協(xié)程先天存在的對于編譯器的復(fù)雜性,感到一絲憂慮,這個(gè)世界上不存在完美的技術(shù),這類編譯器完全內(nèi)部實(shí)現(xiàn)的純純黑盒,對開發(fā)人員來說不太容易掌控到更多的細(xì)節(jié)。
Go 通過外掛計(jì)算??臻g的解決方案,在該 Go 棧空間內(nèi)不保存任何寄存器之類的值,僅存儲調(diào)用函數(shù)棧幀的元RID、參數(shù)、變量等(值或引用),所以在棧空間不足時(shí),進(jìn)行擴(kuò)大外掛棧時(shí)。
即:分配新的??臻g內(nèi)存,并把原棧內(nèi)存復(fù)制過來,在釋放原棧內(nèi)存空間的內(nèi)存,并把新的棧內(nèi)存首地址(指針)掛載到當(dāng)前 Go 協(xié)同程序的棧頂指針、棧底指針。
在復(fù)制并放大?Go 協(xié)程棧內(nèi)存空間的時(shí)候,會導(dǎo)致該協(xié)同被同步阻塞,恢復(fù)取決于這個(gè)步驟在何時(shí)完成。
Go 棧空間雖然不會保存寄存器的值,但并不意味著 Go 程序不會適用目標(biāo)平臺匯編指令集
下述是一個(gè)很簡單的 Go 加法函數(shù),返回參數(shù) x+y 的值:
package mainfunc Add(x int, y int) int {return x + y
}func main() {}
那么 Go 編譯器會輸出以下的匯編指令
TEXT main.Add(SB), NOSPLIT|NOFRAME|ABIInternal, $0-16FUNCDATA $0, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)FUNCDATA $1, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)FUNCDATA $5, main.Add.arginfo1(SB)FUNCDATA $6, main.Add.argliveinfo(SB)PCDATA $3, $1ADDQ BX, AXRETTEXT main.main(SB), NOSPLIT|NOFRAME|ABIInternal, $0-0FUNCDATA $0, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)FUNCDATA $1, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)RET
從上述的代碼中,我們可以清晰的看到,出現(xiàn)了并非X86/X64匯編語法的,FUNCDATA 、PCDATA? 兩個(gè)指令。
它們是 GO 匯編之中的偽指令,注意它是偽指令,意思就是說這東西不能用,除了GO的編譯器能理解它之外,其它的匯編器,無論 GCC、VC++ 都是不認(rèn)識這個(gè)東西。
人們可以理解,Go 存在兩個(gè)編譯過程,一個(gè)前端編譯器,一個(gè)后端編譯器,前端編譯器就是把我們寫的 .go 源文件的程序代碼編譯為 Go 后端編譯器認(rèn)識的 Go 匯編指令集代碼。
這的確很類似于 JAVA/JVM 編譯的字節(jié)碼、C# 編譯器的 MSIL 中間指令代碼,但又存在明顯的區(qū)別,人們可以顯著的參考下述在ARM平臺輸出的 Go 匯編代碼
TEXT main.Add(SB), LEAF|NOFRAME|ABIInternal, $-4-12FUNCDATA $0, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)FUNCDATA $1, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)FUNCDATA $5, main.Add.arginfo1(SB)MOVW main.x(FP), R0MOVW main.y+4(FP), R1ADD R1, R0, R0MOVW R0, main.~r0+8(FP)JMP (R14)TEXT main.main(SB), LEAF|NOFRAME|ABIInternal, $-4-0FUNCDATA $0, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)FUNCDATA $1, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)JMP (R14)
人們可以明顯的看到,除了幾個(gè)偽指令是相同他的,但是內(nèi)部實(shí)現(xiàn)所使用的指令發(fā)生了變化,這是因?yàn)?#xff0c;Go 每個(gè)平臺編譯器生成的 Go 匯編代碼會根據(jù)CPU指令集平臺的不同而不同,這是因?yàn)?Go 雖然編譯的是只能給 Go 后端編譯器看的匯編代碼。
但不意味著它會完全按照先編譯為字節(jié)碼、中間代碼的形式,Go 前端編譯器輸出的 Go 匯編,在編譯的過程中,就已經(jīng)按照目的平臺的指令集進(jìn)行了一部分的翻譯(不完全是真匯編,但匯編已很接近了。)
剩下那部分偽指令是讓 Go 匯編器,在構(gòu)建目的程序時(shí),所需處理的東西,就是GC、外掛棧空間內(nèi)存上面的參數(shù)、局部變量讀取這些實(shí)現(xiàn),最后生成的目的匯編代碼,才是用來編譯為目的PE、ELF可執(zhí)行文件的。
OK:這里簡單的描述下上面X86匯編的意義,ARM我不怎么看得懂,所以不在此處獻(xiàn)丑了
第一句 Go 匯編指令:
TEXT ? ?main.Add(SB), NOSPLIT|NOFRAME|ABIInternal, $0-16
1、TEXT: 這是一個(gè)偽指令,用于指示下面的代碼是函數(shù)代碼(類似于其他匯編語言中的函數(shù)標(biāo)簽)。
2、main.Add(SB): main.Add 是函數(shù)的名稱,SB 表示 Static Base(靜態(tài)基址),它是一個(gè)匯編符號,指示函數(shù)相對于全局?jǐn)?shù)據(jù)區(qū)的偏移量。
3、NOSPLIT|NOFRAME|ABIInternal: 這是函數(shù)的屬性標(biāo)志。NOSPLIT 指示編譯器不應(yīng)在函數(shù)內(nèi)插入棧分裂代碼,NOFRAME 指示編譯器不應(yīng)創(chuàng)建函數(shù)堆棧幀,ABIInternal 表示該函數(shù)的調(diào)用約定為 Go 內(nèi)部使用。
4、$0-16: 這是函數(shù)的棧幀大小指令。$0 表示該函數(shù)不會在棧上分配任何局部變量的空間,-16 表示函數(shù)會從參數(shù)中讀取16字節(jié)的數(shù)據(jù)。
注意:這個(gè)??臻g指的是 Go 程序外掛的棧哈,不是進(jìn)程線程的??臻g。(或?yàn)樘摂M??臻g)
第二句 Go 匯編指令:
FUNCDATA $0, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)
1、這是一個(gè) FUNCDATA 偽指令,用于插入與垃圾回收(garbage collection)相關(guān)的元數(shù)據(jù)。
2、$0 表示這段元數(shù)據(jù)的索引值為 0(參數(shù)位:0 = X)
3、gclocals·g2BeySu+wFnoycgXfElmcg==(SB) 是一個(gè)符號名,它引用了一個(gè)包含局部變量和參數(shù)信息的數(shù)據(jù)結(jié)構(gòu)。
第三句 Go 匯編指令:
FUNCDATA $1, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)
跟第二句沒區(qū)別,元數(shù)據(jù)索引值為 1(參數(shù)位:1 = Y)
第四句 Go 匯編指令:
FUNCDATA $5, main.Add.arginfo1(SB)
main.Add.arginfo1(SB) 是獲取 “描述函數(shù)參數(shù)類型和數(shù)量的數(shù)據(jù)結(jié)構(gòu)的引用地址”。
Go 語言沒有顯示的函數(shù)簽名聲明,所以編譯器需要這個(gè)函數(shù)的參數(shù)信息,以便于可以正確的傳遞參數(shù)值給該函數(shù)。
第五句?Go 匯編指令:
FUNCDATA $6, main.Add.argliveinfo(SB)
main.Add.argliveinfo(SB) 是獲取 “描述函數(shù)參數(shù)活躍性的數(shù)據(jù)結(jié)構(gòu)的引用地址”
參數(shù)的活躍性指的是在函數(shù)執(zhí)行期間哪些參數(shù)被使用了。這些信息對于優(yōu)化代碼的執(zhí)行效率非常重要,GO GC在用。
第六句 Go 匯編指令
PCDATA $3, $1
把 $1?的值復(fù)制到 $3,AT&T匯編風(fēng)格是:
操作數(shù) 原操作數(shù), 目標(biāo)操作數(shù)
加法實(shí)現(xiàn) GO 匯編指令
ADDQ BX, AX
RET
1、AX 和 BX 寄存器用于存儲 x 和 y 的值。
2、之后,通過 ADDQ BX, AX 指令將 y 的值加到 x 上,并將結(jié)果保存在 AX 寄存器中。
3、最后,使用 RET 指令將結(jié)果返回。
總結(jié):
1、Golang 協(xié)程不會保存CPU寄存器的值。
2、Golang 協(xié)程屬于 Stackless 協(xié)程的一種變種。
3、Golang 通過為外掛計(jì)算棧內(nèi)存空間,來實(shí)現(xiàn)類似有棧協(xié)程的效果。
4、Golang 兩個(gè)協(xié)程可能在不同的物理線程上面工作,所以公用數(shù)據(jù)訪問時(shí),須注意同步問題。
5、Golang 協(xié)程在處理異步操作的時(shí),讓出了當(dāng)前協(xié)程占用的線程CPU,協(xié)程處于WAIT狀態(tài)時(shí), 當(dāng)前協(xié)程依賴的外部數(shù)據(jù),可能在外部發(fā)生了改變或者釋放。
? ? ? 所以,該協(xié)程被喚醒之后(resume\awake)理應(yīng)檢查當(dāng)前依賴數(shù)據(jù)的狀態(tài),如:在該協(xié)程處于 Yield 等待狀態(tài)之中時(shí),其它協(xié)程調(diào)用了 Dispose 函數(shù),釋放了 “它(公用數(shù)據(jù))” 持有的全部被托管及非托管資源。
6、Golang 也會適用寄存器優(yōu)化,但這有一些前提,就是簡單的算術(shù)運(yùn)算,可以被編譯為寄存器優(yōu)化的代碼,這不沖突,只是最終會把值存儲到 “Go” 為每個(gè)協(xié)程分配的外掛棧內(nèi)存空間上面。
就像在 MSIL 之中,人們執(zhí)行 stloc.s、ldloc.s、ldarg.s、starg.s 這些指令集一樣,只不過它不像微軟的 .NET CLR 會把這些代碼編譯為近似 C/C++ 編譯器輸出的目標(biāo)平臺匯編代碼,當(dāng)然不管怎么做,這類由GC系統(tǒng)標(biāo)記的語言,都會在最終編譯輸出的匯編代碼之中插入引用技術(shù)管理的實(shí)現(xiàn),區(qū)別是在什么地方插入,當(dāng)然這的看GC系統(tǒng)是怎么設(shè)計(jì)的,比如鏈?zhǔn)奖闅v的GC,就不需要在每個(gè)函數(shù)引用資源的地方去做 AddRef、到結(jié)尾做 ReleaseRef 這樣的行為,但缺點(diǎn)就是GC在處理終結(jié)的時(shí)候,CPU開銷比較大。
7、Golang 之中托管資源是通過RID間接引用的,即托管資源并非是直接使用指針,這是因?yàn)橘Y源或會被GC壓縮或移動碎片整理,當(dāng)然這個(gè)時(shí)候會導(dǎo)致阻塞問題,即:GC Pinned 問題。