日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

简单看看 Go 1.17 的新版调用规约

發(fā)布時(shí)間:2024/4/11 编程问答 53 豆豆
生活随笔 收集整理的這篇文章主要介紹了 简单看看 Go 1.17 的新版调用规约 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

Go 1.17 修改了用了很久的基于棧的調(diào)用規(guī)約,在了解 Go 的調(diào)用規(guī)約之前,我們得知道什么是調(diào)用規(guī)約。

x86 calling convention[1],簡(jiǎn)單概括一下,其實(shí)就是語(yǔ)言對(duì)于函數(shù)之間傳參的一種約定。調(diào)用方要知道我要把參數(shù)按照什么形式,什么順序傳給被調(diào)用函數(shù),被調(diào)用函數(shù)也遵守該規(guī)范去相應(yīng)的位置找到傳入的參數(shù)內(nèi)容。

老版本的 Go 的參數(shù)傳遞圖我們已經(jīng)在很多很多地方見(jiàn)過(guò)了,這里貼一個(gè)我之前畫(huà)的:

可以看到入?yún)⒑头祷刂刀荚跅I?#xff0c;按順序,從低地址,到高地址排列。

這種基于棧的傳參在設(shè)計(jì)和實(shí)現(xiàn)上確實(shí)要簡(jiǎn)單,但棧上傳參會(huì)導(dǎo)致函數(shù)調(diào)用過(guò)程中數(shù)次發(fā)生從寄存器和內(nèi)存之間的參數(shù)搬運(yùn)操作。比如 call 的時(shí)候,要把參數(shù)全搬到 SP 的位置(這里從寄存器 -> 內(nèi)存);ret 的時(shí)候,也要把參數(shù)從寄存器搬到 FP 位置。ret 完畢之后,要把返回值從內(nèi)存 -> 寄存器。

寄存器是 CPU 內(nèi)部的組件,而主存一般都在外部,兩者之間有數(shù)量級(jí)的性能差異,所以一直有人說(shuō) Go 的函數(shù)調(diào)用性能很差,需要優(yōu)化(雖然這些人大概率也不是從系統(tǒng)整體性能考慮去做優(yōu)化的)。

Go 1.17 設(shè)計(jì)了一套基于寄存器傳參的調(diào)用規(guī)約,目前只在 x86 平臺(tái)下開(kāi)啟,我們可以通過(guò)反匯編對(duì)其進(jìn)行簡(jiǎn)單的觀察。這里依然為了簡(jiǎn)化問(wèn)題,我們只用 int 參數(shù)(float 使用的不是通用寄存器,其它數(shù)據(jù)結(jié)構(gòu)需要展開(kāi)傳參,也不難,就是稍微麻煩一點(diǎn))。

package?main//go:noinline func?add(x?int,?y?int,?z?int,?a,?b,?c?int,?d,?e,?f?int,?g,?h,?l?int)?(int,?int,?int,?int,?int,?int,?int,?int,?int,?int,?int)?{ println(x,?y) return?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11 }func?main()?{ println(add(1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12)) }

稍微多傳一些參數(shù)方便我們觀察,輸入 12 個(gè)參數(shù),返回 11 個(gè)值。

直接看反匯編的結(jié)果,首先是對(duì) main.add 的調(diào)用部分:

TEXT?main.main(SB)?/Users/xargin/test/abi.goabi.go:15?????????????0x1054e60???????????????4c8da42478ffffff????????LEAQ?0xffffff78(SP),?R12abi.go:15?????????????0x1054e68???????????????4d3b6610????????????????CMPQ?0x10(R14),?R12abi.go:15?????????????0x1054e6c???????????????0f865a020000????????????JBE?0x10550ccabi.go:15?????????????0x1054e72???????????????4881ec08010000??????????SUBQ?$0x108,?SPabi.go:15?????????????0x1054e79???????????????4889ac2400010000????????MOVQ?BP,?0x100(SP)abi.go:15?????????????0x1054e81???????????????488dac2400010000????????LEAQ?0x100(SP),?BPabi.go:16?????????????0x1054e89???????????????48c704240a000000????????MOVQ?$0xa,?0(SP)?//?第?10?個(gè)參數(shù)abi.go:16?????????????0x1054e91???????????????48c74424080b000000??????MOVQ?$0xb,?0x8(SP)?//?第?11?個(gè)參數(shù)abi.go:16?????????????0x1054e9a???????????????48c74424100c000000??????MOVQ?$0xc,?0x10(SP)?//?第?12?個(gè)參數(shù)abi.go:16?????????????0x1054ea3???????????????b801000000??????????????MOVL?$0x1,?AX?//?第?1?個(gè)參數(shù),后面以此類推abi.go:16?????????????0x1054ea8???????????????bb02000000??????????????MOVL?$0x2,?BXabi.go:16?????????????0x1054ead???????????????b903000000??????????????MOVL?$0x3,?CXabi.go:16?????????????0x1054eb2???????????????bf04000000??????????????MOVL?$0x4,?DIabi.go:16?????????????0x1054eb7???????????????be05000000??????????????MOVL?$0x5,?SIabi.go:16?????????????0x1054ebc???????????????41b806000000????????????MOVL?$0x6,?R8abi.go:16?????????????0x1054ec2???????????????41b907000000????????????MOVL?$0x7,?R9abi.go:16?????????????0x1054ec8???????????????41ba08000000????????????MOVL?$0x8,?R10abi.go:16?????????????0x1054ece???????????????41bb09000000????????????MOVL?$0x9,?R11abi.go:16?????????????0x1054ed4???????????????e807fdffff??????????????CALL?main.add(SB)abi.go:16?????????????0x1054ed9???????????????48898424f8000000????????MOVQ?AX,?0xf8(SP)

可以看到,官方只使用了 9 個(gè)通用寄存器,依次是 AX,BX,CX,DI,SI,R8,R9,R10,R11,超出部分,按順序放在棧上。

然后是 main.add 的返回值部分:

TEXT?main.add(SB)?/Users/xargin/test/abi.go ....??省略?print?的部分abi.go:6??????????????0x1054c2f???????????????48c74424400a000000??????MOVQ?$0xa,?0x40(SP)?//?第?10?個(gè)返回值abi.go:6??????????????0x1054c38???????????????48c74424480b000000??????MOVQ?$0xb,?0x48(SP)?//?第?11?個(gè)返回值abi.go:6??????????????0x1054c41???????????????b801000000??????????????MOVL?$0x1,?AX?//?第?1?個(gè)返回值,后面以此類推abi.go:6??????????????0x1054c46???????????????bb02000000??????????????MOVL?$0x2,?BXabi.go:6??????????????0x1054c4b???????????????b903000000??????????????MOVL?$0x3,?CXabi.go:6??????????????0x1054c50???????????????bf04000000??????????????MOVL?$0x4,?DIabi.go:6??????????????0x1054c55???????????????be05000000??????????????MOVL?$0x5,?SIabi.go:6??????????????0x1054c5a???????????????41b806000000????????????MOVL?$0x6,?R8abi.go:6??????????????0x1054c60???????????????41b907000000????????????MOVL?$0x7,?R9abi.go:6??????????????0x1054c66???????????????41ba08000000????????????MOVL?$0x8,?R10abi.go:6??????????????0x1054c6c???????????????41bb09000000????????????MOVL?$0x9,?R11abi.go:6??????????????0x1054c72???????????????488b6c2418??????????????MOVQ?0x18(SP),?BPabi.go:6??????????????0x1054c77???????????????4883c420????????????????ADDQ?$0x20,?SPabi.go:6??????????????0x1054c7b???????????????c3??????????????????????RET

返回值和輸入使用了完全相同的寄存器序列,同樣在超出 9 個(gè)返回值時(shí),多出的內(nèi)容在棧上返回。

在傳統(tǒng)的調(diào)用規(guī)約中,一般會(huì)區(qū)分 caller saved registers 和 callee saved registers,但在 Go 中,所有寄存器都是 caller saved,也就是由 caller 負(fù)責(zé)保存,在 callee 中不保證不對(duì)其現(xiàn)場(chǎng)進(jìn)行破壞。

這里可以看到,返回值直接?把入?yún)⑹褂玫募拇嫫??覆蓋掉了,也可以證明這一點(diǎn)。

因?yàn)楹瘮?shù)調(diào)用不需要通過(guò)棧來(lái)傳參了,所以在一些函數(shù)調(diào)用嵌套層次比較深的場(chǎng)景下,goroutine 棧本身使用的內(nèi)存也有一定概率會(huì)降低。不過(guò)因?yàn)闀簳r(shí)手邊沒(méi)有什么生產(chǎn)環(huán)境,暫時(shí)也無(wú)法驗(yàn)證就是了。

在 Go 語(yǔ)言中,除了基本的函數(shù)調(diào)用傳參,在 reflect 包中有 reflect.Call 的 API 也可以執(zhí)行函數(shù)調(diào)用;此外還有 defer 語(yǔ)句中也可以執(zhí)行函數(shù)調(diào)用,在創(chuàng)建 goroutine 時(shí)也涉及給函數(shù)傳參。

我們同樣可以使用反匯編的手段,來(lái)觀察這些場(chǎng)景下是否已經(jīng)進(jìn)行了基于寄存器的調(diào)用優(yōu)化,本文就不再贅述了。

[1]

x86 calling convention:?https://en.wikipedia.org/wiki/X86_calling_conventions

總結(jié)

以上是生活随笔為你收集整理的简单看看 Go 1.17 的新版调用规约的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。