Hvad er et Managed Language?
Hvad er et managed sprog?
Et managed sprog er, i modsætning til et unmanaged sprog – altså et sprog, der blot udfører den logik, programmøren har skrevet, uden større modifikationer – et sprog, der eksekverer GC, runtime-optimering, green threads, håndtering af concurrency osv. i runtime, hvilket gør det unødvendigt for brugeren at foretage risikabel low-level administration.
For sådanne sprog er fordelen, at man kan koncentrere sig udelukkende om forretningslogik og fordybe sig i udviklingen, men på den anden side kan programmet opføre sig anderledes end programmørens intuition tilsiger, hvilket af og til nødvendiggør sofistikeret runtime-tuning.
Først vil vi betragte Go, som er et af de managed sprog, der er mest tro mod minimalistisk filosofi og har en transparent assembly.
Go-sprogets binære struktur
| .text | .data | .gopclntab, .typelink etc. |
|---|---|---|
| Maskinkode til eksekvering | Data til lagring | Sprog-runtime-sektion |
Da Go-sproget ikke oversætter maskinkode 1:1 baseret på brugerens input, er logikken i .text-sektionen tæt forbundet med sprogets runtime-sektion.
Desuden tilføjes funktioner, som brugeren ikke selv har skrevet, såsom runtime.printnl(), til .text-sektionens assembly.
Gennem denne automatiske kodeindsættelse hjælper Go-sproget udvikleren med at undgå manuel administration.
Betragtning af main-funktionen i Go
Lad os først oprette et simpelt eksempel i main.go og se på main-funktionen på en AMD64-maskine.
1package main
2
3func sayHello(msg string) {
4 println(msg)
5}
6
7func main() {
8 sayHello("Hello World")
9}
Derefter bygges det således.
1go build main.go
Go understøtter go tool for at lette low-level debugging.
For kun at se assemblyen for main-funktionen i main-pakken via go tool, indtastes følgende kommando.
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)
- Efter at have sammenlignet med
CMPQom den aktuelle tråd er nået, springes der til Entrypoint 0x468f95, hvis betingelsen er opfyldt. - Indgangspunktet indsættes på stakken med
PUSHQ BP. - Der angives et startpunkt for stakken ved funktionsstart i det register (SP), hvor data senest er blevet indlæst, for at fiksere indgangspunktet ved reference til lokale variabler.
- Derefter reserveres 16 bytes til lokal variabelstak (
SUBQ $0x10, SP), og der udfyldes med bytes viaNOPLfor at sikre CPU-cache-alignment. - Go Runtime låser output til strengbufferen ved at kalde
runtime.printlock(SB). - Ved brug af
LEAQ-instruktionen gemmes startadressen for den tildelte streng i AX, som er akkumulatorregisteret til datalagring blandt de generelle registre. - Derefter gemmes strenglængden 11 i BX-registeret, som bruges til beregningsstøtte og midlertidig datalagring. (
MOVL $0Xb, BX) - Akkumulatorinformationen sendes til SB via
runtime.printstring(SB). - Et linjeskift skrives også til SB via
runtime.printnl(SB). - Strengbufferen frigives via
runtime.printunlock(SB). - Den lånte 16-byte stak-hukommelse returneres med
ADDQ $0x10, SP. Da indgangspunktet oprindeligt blev placeret på stakken, fjernes det nu medPOPQ BP, før retursignalet gives. - Derefter opsættes runtime, såsom staktildeling og GC, via
runtime.morestack_noctxt.abi0(SB), som det sig hør og bør for et managed sprog. - Der navigeres til den managed
main.main(SB)-adresse.
Som det ses, er assemblyen for forretningslogikken ret overskuelig og fremstår blot med et tyndt lag af runtime-administration.
Uden optimering
Ovenstående form er et resultat af, at Go-kompileren automatisk har inlinet to separate funktioner. For læringens skyld vil vi dog i dette tilfælde undlade at inline sayHello.
For at opnå dette kompileres kilden med følgende flag.
1 go build -gcflags="-l" main.go
Ved at udskrive resultatet i shellen, findes der duplikerede assembly-instruktioner.
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 således bekræftet, at kompilatorens optimeringer er rettet mod netop sådanne redundante operationer, ineffektiv loop-unrolling osv.
Næste gang
Næste gang vil vi gennemgå if- og switch-statements i Go-sproget. Hvis tiden tillader det senere, planlægger jeg også at analysere Go runtime-sektionerne.