GoSuda

Mi az a managed language?

By Lee Yunjin
views ...

Mi a managed nyelv?

A managed nyelv – ellentétben az unmanaged nyelvekkel, amelyek lényegében nem térnek el a programozó által megírt logikától, csupán végrehajtják azt – olyan nyelv, amely futásidőben hajt végre GC-t (Garbage Collection), futásidejű optimalizálást, green thread kezelést, valamint konkurens műveleteket, ezáltal mentesítve a felhasználót a veszélyes, alacsony szintű erőforrás-kezeléstől.

Az ilyen nyelvek esetében előny, hogy a fejlesztő kizárólag a business logic-ra koncentrálhat, ugyanakkor hátrányuk, hogy a program a programozó intuíciójától eltérően is működhet, ami esetenként kifinomult futásidejű tuningot tesz szükségessé.

Először a managed nyelvek közül a leginkább minimalista filozófiát követő és az assembly tekintetében legőszintébb Go nyelvet vizsgáljuk meg.

A Go nyelv bináris struktúrája

.text.data.gopclntab, .typelink stb.
Végrehajtandó gépi kódTárolandó adatokNyelvi runtime szekciók

Mivel a Go nyelv nem 1:1 arányban fordítja a felhasználói bemenetet gépi kódra, a .text szekció logikája szorosan összefonódik a nyelvi runtime szekciókkal.

Emellett olyan függvények is bekerülnek a .text szekció assemblyjébe, amelyeket a felhasználó nem írt meg, mint például a runtime.printnl(). Ezen automatikus kódbeillesztések révén a Go nyelv segít a fejlesztőnek megszabadulni a kézi menedzselés terhétől.

A main függvény vizsgálata Go nyelven

Először is írjunk egy egyszerű main.go forráskódot, és vizsgáljuk meg a main függvényt AMD64 architektúrán.

1package main
2
3func sayHello(msg string) {
4    println(msg)
5}
6
7func main() {
8    sayHello("Hello World")
9}

Ezt követően az alábbi módon építjük (build) fel:

1go build main.go

A Go az egyszerű alacsony szintű hibakeresés érdekében támogatja a go tool használatát. A go tool segítségével a main csomagból a main függvényre vonatkozó assembly megtekintéséhez az alábbi parancsot adjuk meg:

1go tool objdump -s "main\.main" ./main

Assembly

 1TEXT main.main(SB) /home/yjlee/compare-assembly/go/main.go
 2  main.go:7             0x468f60                493b6610                CMPQ SP, 0x10(R14)
 3  main.go:7             0x468f64                762f                    JBE 0x468f95
 4  main.go:7             0x468f66                55                      PUSHQ BP
 5  main.go:7             0x468f67                4889e5                  MOVQ SP, BP
 6  main.go:7             0x468f6a                4883ec10                SUBQ $0x10, SP
 7  main.go:8             0x468f6e                90                      NOPL
 8  main.go:4             0x468f6f                e8cca3fcff              CALL runtime.printlock(SB)
 9  main.go:4             0x468f74                488d05da290100          LEAQ 0x129da(IP), AX
10  main.go:4             0x468f7b                bb0b000000              MOVL $0xb, BX
11  main.go:4             0x468f80                e83bacfcff              CALL runtime.printstring(SB)
12  main.go:4             0x468f85                e8f6a5fcff              CALL runtime.printnl(SB)
13  main.go:4             0x468f8a                e811a4fcff              CALL runtime.printunlock(SB)
14  main.go:9             0x468f8f                4883c410                ADDQ $0x10, SP
15  main.go:9             0x468f93                5d                      POPQ BP
16  main.go:9             0x468f94                c3                      RET
17  main.go:7             0x468f95                e8e6afffff              CALL runtime.morestack_noctxt.abi0(SB)
18  main.go:7             0x468f9a                ebc4                    JMP main.main(SB)
  • A CMPQ utasítással összehasonlítjuk, hogy a jelenlegi szálra léptünk-e be, majd ha igen, ugrunk a 0x468f95 belépési pontra (Entrypoint).
  • A belépési pontot a PUSHQ BP utasítással helyezzük a stack-re.
  • A legutóbb adatot tároló SP regiszterben a függvény kezdetekor kijelöljük a stack kezdőpontját, így rögzítve a lokális változók referenciájának belépési pontját.
  • Ezt követően lefoglalunk 16 bájtot a lokális változók stack-je számára (SUBQ $0x10, SP), majd a NOPL utasítással több bájtot kitöltve elvégezzük a CPU cache igazítását.
  • A Go Runtime-ban a runtime.printlock(SB) meghívásával zároljuk a string buffer kimenetét.
  • A LEAQ utasítással a lefoglalt karakterlánc kezdőcímét elmentjük az AX regiszterbe, amely az általános célú regiszterek közül az adattárolásra használt akkumulátor.
  • Ezt követően a BX regiszterbe, amelyet műveleti segédletként és ideiglenes adattárolásra használunk, elmentjük a karakterlánc 11-es hosszát. (MOVL $0Xb, BX)
  • A runtime.printstring(SB) segítségével az akkumulátor információit az SB felé írjuk ki.
  • Egy üres sort szintén a runtime.printnl(SB) segítségével írunk az SB felé.
  • A string buffert a runtime.printunlock(SB) segítségével oldjuk fel.
  • Az ADDQ $0x10, SP utasítással visszaadjuk a kölcsönvett 16 bájtos stack memóriát. Mivel a belépési pontot a stack-re helyezve jeleztük, a POPQ BP utasítással kivesszük azt a stack-ből, majd kiadjuk a visszatérési szignált.
  • Ezt követően a runtime.morestack_noctxt.abi0(SB) segítségével, managed nyelvhez méltóan, lefoglaljuk a megfelelő stack-et és beállítjuk a runtime-ot, beleértve a GC-t is.
  • Végül a vezérlés átkerül a menedzselt main.main(SB) címre.

Amint látható, a business logic assemblyje meglehetősen világos, csupán egy vékony runtime kezelés rétegeződik rá.

Optimalizálás nélkül

A fenti forma a Go fordító által automatikusan elvégzett inlining eredménye, ahol a két különálló függvényt összefűzte. Tanulási célból azonban ebben az esetben tiltsuk le a sayHello inliningját.

Ehhez az alábbi flag-gel fordítjuk a forráskódot:

1 go build -gcflags="-l" main.go

A shellben az eredményeket vizsgálva duplikált assembly kód fedezhető fel.

 1yjlee@elegant:~/compare-assembly/go$ go build -gcflags="-l" main.go
 2
 3go tool objdump -s "main\.sayHello" ./main
 4TEXT main.sayHello(SB) /home/yjlee/compare-assembly/go/main.go
 5  main.go:3             0x468f60                493b6610               CMPQ SP, 0x10(R14)
 6  main.go:3             0x468f64                7636                   JBE 0x468f9c
 7  main.go:3             0x468f66                55                     PUSHQ BP
 8  main.go:3             0x468f67                4889e5                 MOVQ SP, BP
 9  main.go:3             0x468f6a                4883ec10               SUBQ $0x10, SP
10  main.go:5             0x468f6e                4889442420             MOVQ AX, 0x20(SP)
11  main.go:5             0x468f73                48895c2428             MOVQ BX, 0x28(SP)
12  main.go:4             0x468f78                e8c3a3fcff             CALL runtime.printlock(SB)
13  main.go:4             0x468f7d                488b442420             MOVQ 0x20(SP), AX
14  main.go:4             0x468f82                488b5c2428             MOVQ 0x28(SP), BX
15  main.go:4             0x468f87                e834acfcff             CALL runtime.printstring(SB)
16  main.go:4             0x468f8c                e8efa5fcff             CALL runtime.printnl(SB)
17  main.go:4             0x468f91                e80aa4fcff             CALL runtime.printunlock(SB)
18  main.go:5             0x468f96                4883c410               ADDQ $0x10, SP
19  main.go:5             0x468f9a                5d                     POPQ BP
20  main.go:5             0x468f9b                c3                     RET
21  main.go:3             0x468f9c                4889442408             MOVQ AX, 0x8(SP)
22  main.go:3             0x468fa1                48895c2410             MOVQ BX, 0x10(SP)
23  main.go:3             0x468fa6                e8d5afffff             CALL runtime.morestack_noctxt.abi0(SB)
24  main.go:3             0x468fab                488b442408             MOVQ 0x8(SP), AX
25  main.go:3             0x468fb0                488b5c2410             MOVQ 0x10(SP), BX
26  main.go:3             0x468fb5                eba9                   JMP main.sayHello(SB)
27yjlee@elegant:~/compare-assembly/go$ go tool objdump -s "main\.sayHello" ./main
28TEXT main.sayHello(SB) /home/yjlee/compare-assembly/go/main.go
29  main.go:3             0x468f60                493b6610               CMPQ SP, 0x10(R14)
30  main.go:3             0x468f64                7636                   JBE 0x468f9c
31  main.go:3             0x468f66                55                     PUSHQ BP
32  main.go:3             0x468f67                4889e5                 MOVQ SP, BP
33  main.go:3             0x468f6a                4883ec10               SUBQ $0x10, SP
34  main.go:5             0x468f6e                4889442420             MOVQ AX, 0x20(SP)
35  main.go:5             0x468f73                48895c2428             MOVQ BX, 0x28(SP)
36  main.go:4             0x468f78                e8c3a3fcff             CALL runtime.printlock(SB)
37  main.go:4             0x468f7d                488b442420             MOVQ 0x20(SP), AX
38  main.go:4             0x468f82                488b5c2428             MOVQ 0x28(SP), BX
39OVQ 0x20(SP), AX
40  main.go:4             0x468f82                488b5c2428             MOVQ 0x28(SP), BX
41  main.go:4             0x468f87                e834acfcff             CALL runtime.printstring(SB)
42  main.go:4             0x468f8c                e8efa5fcff             CALL runtime.printnl(SB)
43  main.go:4             0x468f91                e80aa4fcff             CALL runtime.printunlock(SB)
44  main.go:5             0x468f96                4883c410               ADDQ $0x10, SP
45  main.go:5             0x468f9a                5d                     POPQ BP
46  main.go:5             0x468f9b                c3                     RET
47  main.go:3             0x468f9c                4889442408             MOVQ AX, 0x8(SP)
48  main.go:3             0x468fa1                48895c2410             MOVQ BX, 0x10(SP)
49  main.go:3             0x468fa6                e8d5afffff             CALL runtime.morestack_noctxt.abi0(SB)
50  main.go:3             0x468fab                488b442408             MOVQ 0x8(SP), AX
51  main.go:3             0x468fb0                488b5c2410             MOVQ 0x10(SP), BX
52  main.go:3             0x468fb5                eba9                   JMP main.sayHello(SB)
53yjlee@elegant:~/compare-assembly/go$

Ebből megállapítható, hogy a fordító optimalizációs tevékenysége az ilyen redundáns műveletek, valamint a nem hatékony loop unrolling kiküszöbölésére irányul.

Következő alkalommal

A következő részben a Go nyelv if és switch utasításaival foglalkozunk. Ha az idő engedi, a későbbiekben a Go runtime szekcióinak elemzésére is sort kerítünk.