slice是何時決定要擴張?

slice是何時決定要擴張?

網上說slice的文章已經不少了,大都已經把slice的內存擴張原理都說清楚了。可是是如何判斷slice是否須要擴張這個點卻沒有說的很清楚。想固然的我會以爲這個append是否擴張的邏輯應該隱藏在runtime中的某個函數,根據append的數組的長度進行判斷。可是是不是如此呢?數組

本着這個疑問,我作了以下的實驗。app

我寫了兩個方法,一個須要擴張,一個不須要擴張。函數

無需擴張

不須要擴張的代碼以下:優化

package main

func main() {
        a := make([]int, 1, 3)
        a = append(a, 4)
        println(a)
}

使用 go tool objdump 來打印出編譯後的main彙編碼:編碼

TEXT main.main(SB) /Users/yejianfeng/Documents/gopath/src/demo/append.go
  append.go:3       0x104e140       65488b0c2530000000  MOVQ GS:0x30, CX
  append.go:3       0x104e149       483b6110        CMPQ 0x10(CX), SP
  append.go:3       0x104e14d       7661            JBE 0x104e1b0
  append.go:3       0x104e14f       4883ec38        SUBQ $0x38, SP
  append.go:3       0x104e153       48896c2430      MOVQ BP, 0x30(SP)
  append.go:3       0x104e158       488d6c2430      LEAQ 0x30(SP), BP
  append.go:4       0x104e15d       48c744241800000000  MOVQ $0x0, 0x18(SP)
  append.go:4       0x104e166       0f57c0          XORPS X0, X0
  append.go:4       0x104e169       0f11442420      MOVUPS X0, 0x20(SP)
  append.go:5       0x104e16e       48c744242004000000  MOVQ $0x4, 0x20(SP)
  append.go:6       0x104e177       e86445fdff      CALL runtime.printlock(SB)
  append.go:6       0x104e17c       488d442418      LEAQ 0x18(SP), AX
  append.go:6       0x104e181       48890424        MOVQ AX, 0(SP)
  append.go:6       0x104e185       48c744240802000000  MOVQ $0x2, 0x8(SP)
  append.go:6       0x104e18e       48c744241003000000  MOVQ $0x3, 0x10(SP)
  append.go:6       0x104e197       e8f44efdff      CALL runtime.printslice(SB)
  append.go:6       0x104e19c       e8bf47fdff      CALL runtime.printnl(SB)
  append.go:6       0x104e1a1       e8ba45fdff      CALL runtime.printunlock(SB)
  append.go:7       0x104e1a6       488b6c2430      MOVQ 0x30(SP), BP
  append.go:7       0x104e1ab       4883c438        ADDQ $0x38, SP
  append.go:7       0x104e1af       c3          RET
  append.go:3       0x104e1b0       e82b89ffff      CALL runtime.morestack_noctxt(SB)
  append.go:3       0x104e1b5       eb89            JMP main.main(SB)

這個彙編碼的邏輯在append.go第5行就只有一個MOV指令,將4直接放到指定的內存地址。rest

須要擴張

個人另外一個須要擴張的代碼以下:code

package main

func main() {
        a := make([]int, 1, 1)
        a = append(a, 4)
        println(a)
}

生成的彙編碼以下:內存

TEXT main.main(SB) /Users/yejianfeng/Documents/gopath/src/demo/append.go
  append.go:3       0x104e140       65488b0c2530000000  MOVQ GS:0x30, CX
  append.go:3       0x104e149       483b6110        CMPQ 0x10(CX), SP
  append.go:3       0x104e14d       0f86b0000000        JBE 0x104e203
  append.go:3       0x104e153       4883ec68        SUBQ $0x68, SP
  append.go:3       0x104e157       48896c2460      MOVQ BP, 0x60(SP)
  append.go:3       0x104e15c       488d6c2460      LEAQ 0x60(SP), BP
  append.go:5       0x104e161       48c744245000000000  MOVQ $0x0, 0x50(SP)
  append.go:5       0x104e16a       488d05af9d0000      LEAQ type.*+40128(SB), AX
  append.go:5       0x104e171       48890424        MOVQ AX, 0(SP)
  append.go:5       0x104e175       488d442450      LEAQ 0x50(SP), AX
  append.go:5       0x104e17a       4889442408      MOVQ AX, 0x8(SP)
  append.go:5       0x104e17f       48c744241001000000  MOVQ $0x1, 0x10(SP)
  append.go:5       0x104e188       48c744241801000000  MOVQ $0x1, 0x18(SP)
  append.go:5       0x104e191       48c744242002000000  MOVQ $0x2, 0x20(SP)
  append.go:5       0x104e19a       e8b16bfeff      CALL runtime.growslice(SB)
  append.go:5       0x104e19f       488b442428      MOVQ 0x28(SP), AX
  append.go:5       0x104e1a4       4889442458      MOVQ AX, 0x58(SP)
  append.go:5       0x104e1a9       488b4c2430      MOVQ 0x30(SP), CX
  append.go:5       0x104e1ae       48894c2448      MOVQ CX, 0x48(SP)
  append.go:5       0x104e1b3       488b542438      MOVQ 0x38(SP), DX
  append.go:5       0x104e1b8       4889542440      MOVQ DX, 0x40(SP)
  append.go:5       0x104e1bd       48c7400804000000    MOVQ $0x4, 0x8(AX)
  append.go:6       0x104e1c5       e81645fdff      CALL runtime.printlock(SB)
  append.go:6       0x104e1ca       488b442458      MOVQ 0x58(SP), AX
  append.go:6       0x104e1cf       48890424        MOVQ AX, 0(SP)
  append.go:5       0x104e1d3       488b442448      MOVQ 0x48(SP), AX
  append.go:5       0x104e1d8       48ffc0          INCQ AX
  append.go:6       0x104e1db       4889442408      MOVQ AX, 0x8(SP)
  append.go:6       0x104e1e0       488b442440      MOVQ 0x40(SP), AX
  append.go:6       0x104e1e5       4889442410      MOVQ AX, 0x10(SP)
  append.go:6       0x104e1ea       e8a14efdff      CALL runtime.printslice(SB)
  append.go:6       0x104e1ef       e86c47fdff      CALL runtime.printnl(SB)
  append.go:6       0x104e1f4       e86745fdff      CALL runtime.printunlock(SB)
  append.go:7       0x104e1f9       488b6c2460      MOVQ 0x60(SP), BP
  append.go:7       0x104e1fe       4883c468        ADDQ $0x68, SP

這裏的第5行就和以前的那個大不同了。有很是多的邏輯。基本進入第五行作的事情就是開始準備調用runtime.growslice的邏輯了編譯器

append.go:5     0x104e161       48c744245000000000  MOVQ $0x0, 0x50(SP)
append.go:5     0x104e16a       488d05af9d0000      LEAQ type.*+40128(SB), AX
append.go:5     0x104e171       48890424        MOVQ AX, 0(SP)
append.go:5     0x104e175       488d442450      LEAQ 0x50(SP), AX
append.go:5     0x104e17a       4889442408      MOVQ AX, 0x8(SP)
append.go:5     0x104e17f       48c744241001000000  MOVQ $0x1, 0x10(SP)
append.go:5     0x104e188       48c744241801000000  MOVQ $0x1, 0x18(SP)
append.go:5     0x104e191       48c744242002000000  MOVQ $0x2, 0x20(SP)
append.go:5     0x104e19a       e8b16bfeff      CALL runtime.growslice(SB)

這裏就很明顯了,因此slice的append是否進行cap擴張是在編譯器進行判斷的?至少我上面的兩個代碼,編譯器編譯的時候是知道這個slice是否須要進行擴張的,根據是否進行擴張就決定是否調用growslice。for循環

再複雜的case

在雨痕羣裏問了下這個問題,有位羣友給了個更爲複雜點的case:

package main

func main() {
        a := make([]int, 1, 5)
        b := 3
        for i := 0; i < b; i++ {
                a = append(a, 4)
        }
        println(a)
}

這裏的append是包圍在for循環裏面的,編譯器其實就很難判斷了。咱們看下彙編:

TEXT main.main(SB) /Users/yejianfeng/Documents/gopath/src/demo/append.go
  append.go:3       0x104e140       65488b0c2530000000  MOVQ GS:0x30, CX
  append.go:3       0x104e149       488d4424f0      LEAQ -0x10(SP), AX
  append.go:3       0x104e14e       483b4110        CMPQ 0x10(CX), AX
  append.go:3       0x104e152       0f86fb000000        JBE 0x104e253
  append.go:3       0x104e158       4881ec90000000      SUBQ $0x90, SP
  append.go:3       0x104e15f       4889ac2488000000    MOVQ BP, 0x88(SP)
  append.go:3       0x104e167       488dac2488000000    LEAQ 0x88(SP), BP
  append.go:4       0x104e16f       48c744245800000000  MOVQ $0x0, 0x58(SP)
  append.go:4       0x104e178       0f57c0          XORPS X0, X0
  append.go:4       0x104e17b       0f11442460      MOVUPS X0, 0x60(SP)
  append.go:4       0x104e180       0f11442470      MOVUPS X0, 0x70(SP)
  append.go:4       0x104e185       31c0            XORL AX, AX
  append.go:4       0x104e187       488d4c2458      LEAQ 0x58(SP), CX
  append.go:4       0x104e18c       ba01000000      MOVL $0x1, DX
  append.go:4       0x104e191       bb05000000      MOVL $0x5, BX
  append.go:6       0x104e196       eb0e            JMP 0x104e1a6
  append.go:7       0x104e198       48c704d104000000    MOVQ $0x4, 0(CX)(DX*8)
  append.go:6       0x104e1a0       48ffc0          INCQ AX
  append.go:9       0x104e1a3       4889f2          MOVQ SI, DX
  append.go:9       0x104e1a6       4889542448      MOVQ DX, 0x48(SP)
  append.go:6       0x104e1ab       4883f803        CMPQ $0x3, AX
  append.go:6       0x104e1af       7d51            JGE 0x104e202
  append.go:7       0x104e1b1       488d7201        LEAQ 0x1(DX), SI
  append.go:7       0x104e1b5       4839de          CMPQ BX, SI
  append.go:7       0x104e1b8       7ede            JLE 0x104e198
  append.go:6       0x104e1ba       4889442440      MOVQ AX, 0x40(SP)
  append.go:7       0x104e1bf       488d05ba9d0000      LEAQ type.*+40128(SB), AX
  append.go:7       0x104e1c6       48890424        MOVQ AX, 0(SP)
  append.go:7       0x104e1ca       48894c2408      MOVQ CX, 0x8(SP)
  append.go:7       0x104e1cf       4889542410      MOVQ DX, 0x10(SP)
  append.go:7       0x104e1d4       48895c2418      MOVQ BX, 0x18(SP)
  append.go:7       0x104e1d9       4889742420      MOVQ SI, 0x20(SP)
  append.go:7       0x104e1de       e86d6bfeff      CALL runtime.growslice(SB)
  append.go:7       0x104e1e3       488b4c2428      MOVQ 0x28(SP), CX
  append.go:7       0x104e1e8       488b442430      MOVQ 0x30(SP), AX
  append.go:7       0x104e1ed       488b5c2438      MOVQ 0x38(SP), BX
  append.go:7       0x104e1f2       488d7001        LEAQ 0x1(AX), SI
  append.go:6       0x104e1f6       488b442440      MOVQ 0x40(SP), AX
  append.go:7       0x104e1fb       488b542448      MOVQ 0x48(SP), DX
  append.go:7       0x104e200       eb96            JMP 0x104e198
  append.go:9       0x104e202       48898c2480000000    MOVQ CX, 0x80(SP)
  append.go:9       0x104e20a       48895c2450      MOVQ BX, 0x50(SP)
  append.go:9       0x104e20f       e8cc44fdff      CALL runtime.printlock(SB)

重點看這一行:

append.go:7       0x104e1b5       4839de          CMPQ BX, SI
  append.go:7       0x104e1b8       7ede            JLE 0x104e198

BX裏面存的是a如今的cap值,(能夠從MOVL $0x5, BX看出來)。而SI裏面存儲的是老的slice的長度(DX)加1以後的值,就是新的slice須要的len值。因此上面兩句的意思就是比較下新的len和cap的大小,若是len小於cap的話,就跳到0x104e198,就是直接執行MOVE操做,不然的話,就開始準備growslice。

總結

上面的分析說明,slice是否須要擴張的邏輯是編譯器作的,而且編譯器若是能直接判斷是否這個slice須要擴張,就直接將是否須要擴張的結果做爲編譯結果。不然的話,就將這個if else的邏輯寫在編譯結果裏面,在runtime時候跳轉判斷。

到這裏我有點理解編譯器和運行時的邊界。其實本質上,兩個步驟都是爲了代碼更快得出結果,編譯器優化的越多,運行過程執行的速度就越快,固然編譯器同時也須要兼顧生成的可執行文件的大小問題等。對一個語言,編譯器優化,是個很重要的工做。

相關文章
相關標籤/搜索