Hva er et managed language?
Hva er et managed språk?
Et managed språk er, i motsetning til et unmanaged språk – det vil si et språk som kun utfører logikken programmert av programmereren uten vesentlige avvik – et språk som utfører GC, runtime-optimalisering, green threads, håndtering av samtidighet og lignende i sin runtime. Dette eliminerer behovet for at brukeren må utføre risikabel lavnivåstyring.
For slike språk er fordelen at man kan fokusere utelukkende på forretningslogikk og fordype seg i utviklingen, men på den annen side kan det hende at programmet oppfører seg annerledes enn det programmererens intuisjon skulle tilsi, noe som av og til nødvendiggjør presis runtime-tuning.
Først skal vi se på Go, et språk blant de managed språkene som er mest tro mot en minimalistisk filosofi og som har en ærlig assembly-kode.
Binærstruktur i Go
| .text | .data | .gopclntab, .typelink m.m. |
|---|---|---|
| Maskinkode som skal utføres | Data som skal lagres | Språkets runtime-seksjoner |
Siden Go ikke oversetter maskinkode 1:1 slik brukeren har skrevet den, er logikken i .text-seksjonen tett knyttet til språkets runtime-seksjoner.
Videre legges funksjoner som runtime.printnl(), som brukeren ikke har skrevet eksplisitt, til i .text-seksjonens assembly.
Gjennom denne automatiske kodeinnsettingen hjelper Go-språket utvikleren med å slippe manuell administrasjon.
Se kun på main-funksjonen i Go
Først, la oss skrive et enkelt eksempel i kildekoden main.go og se på den fra main på en AMD64-maskin.
1package main
2
3func sayHello(msg string) {
4 println(msg)
5}
6
7func main() {
8 sayHello("Hello World")
9}
Deretter bygges det slik.
1go build main.go
Go støtter go tool for enklere lavnivå-debugging.
For å se kun assembly-koden tilhørende main-funksjonen i main-pakken via go tool, skriver man inn denne kommandoen.
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)
- Etter å ha sammenlignet om man har gått inn i gjeldende tråd med CMPQ, hopper den til Entrypoint 0x468f95 hvis betingelsen er oppfylt.
- Inngangspunktet settes inn i stacken med
PUSHQ BP. - Ved funksjonsstart spesifiseres stackens startpunkt i registeret SP, hvor data sist ble lastet, for å fiksere inngangspunktet ved referanse til lokale variabler.
- Deretter reserveres 16 bytes med stack for lokale variabler (
SUBQ $0x10, SP), og NOPL brukes til å fylle ut flere bytes for CPU-cache-alignment. - Go Runtime låser utgangen til strengbufferen ved å kalle
runtime.printlock(SB). - Ved bruk av LEAQ-instruksjonen lagres startadressen til den allokerte strengen i AX, som er akkumulatoradressen som brukes til datalagring blant generelle registre.
- Deretter lagres strenglengden 11 i BX-registeret, som brukes til operasjonsstøtte og midlertidig datalagring. (
MOVL $0Xb, BX) - Akkumulatorinformasjonen skrives til SB med runtime.printstring(SB).
- Et linjeskift skrives også til SB via runtime.printnl(SB).
- Strengbufferen frigjøres med runtime.printunlock(SB).
- ADDQ $0x10, SP returnerer den lånte 16-byte stack-minneplassen. – Siden inngangspunktet ble lagt i stacken i begynnelsen, fjernes det nå fra stacken med POPQ BP før retursignalet gis.
- Deretter settes tilstrekkelig stack opp for et managed språk, inkludert setup av runtime som GC, ved hjelp av runtime.morestack_noctxt.abi0(SB).
- Man flytter seg til den administrerte adressen main.main(SB).
Som vist er assembly-koden for forretningslogikken ganske oversiktlig, med kun lett runtime-administrasjon lagt til.
Uten optimalisering
Formen ovenfor er resultatet av at Go-kompilatoren automatisk har utført inlining på to separate funksjoner for å optimalisere dem. For læringens skyld vil vi imidlertid i dette tilfellet hindre at sayHello blir inlinet.
For å gjøre dette, kompilerer vi kildekoden med følgende flagg.
1 go build -gcflags="-l" main.go
Når resultatet skrives ut i skallet, oppdages overlappende assembly-kode.
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$
Det er altså bekreftet at det kompilatoren optimaliserer, er nettopp slike redundante operasjoner, ineffektiv loop-unrolling og lignende.
Neste gang
Neste gang skal vi ta for oss if-setninger og switch-setninger i Go. Hvis tiden tillater det senere, vil vi også analysere Go runtime-seksjonene.