Vad är ett managed language?
Vad är ett managed language?
Ett managed language är, till skillnad från ett unmanaged language – det vill säga ett språk där exekveringen inte avviker nämnvärt från den logik som programmeraren skrivit – ett språk som exekverar GC, runtime-optimering, green threads, samt hantering av concurrency i sin runtime, vilket gör att användaren slipper utföra riskabel lågnivåhantering.
För sådana språk är fördelen att utvecklare kan fokusera enbart på affärslogik, men å andra sidan kan det faktiska programmet bete sig annorlunda än vad programmerarens intuition antyder, vilket ibland kräver sofistikerad runtime-tuning.
Först ska vi betrakta Go, ett språk bland de managed languages som är mest troget en minimalistisk filosofi och vars assembly är transparent.
Binärstruktur i Go
| .text | .data | .gopclntab, .typelink etc. |
|---|---|---|
| Maskinkod som ska exekveras | Data som ska lagras | Språkets runtime-sektioner |
Eftersom Go inte översätter maskinkod 1:1 utifrån användarens input, är logiken i .text-sektionen nära kopplad till språkets runtime-sektioner.
Dessutom läggs funktioner som rumtime.printnl(), vilka användaren inte skrivit explicit, till i .text-sektionens assembly. Genom denna automatiska kodinjektion hjälper Go utvecklaren att frigöra sig från manuell hantering.
Betraktelse av main-funktionen i Go
Låt oss först skapa ett enkelt exempel, källkoden main.go, och undersöka main från grunden på en AMD64-maskin.
1package main
2
3func sayHello(msg string) {
4 println(msg)
5}
6
7func main() {
8 sayHello("Hello World")
9}
Därefter kompilerar vi den enligt följande:
1go build main.go
Go tillhandahåller go tool för att underlätta felsökning på låg nivå.
För att endast se assemblyn för main-funktionen i main-paketet med go tool, anger vi följande 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 att ha jämfört med CMPQ om den nuvarande tråden har påbörjats, hoppar exekveringen till Entrypoint 0x468f95 om villkoret är uppfyllt.
- Ingångspunkten skjuts in (PUSHQ BP) på stacken.
- Vid funktionsstart anges stackens startpunkt i registret SP, där data senast laddades, för att fixera ingångspunkten vid referens till lokala variabler.
- Därefter reserveras 16 byte för en lokal variabelstack (
SUBQ $0x10, SP), och NOPL används för att fylla ut med byte för CPU-cache-justering. - Go Runtime anropar
runtime.printlock(SB)för att låsa utmatningen för strängbufferten. - Instruktionen LEAQ används för att lagra startadressen för den allokerade strängen i AX, en ackumulator som används för datalagring bland de generella registren.
- Därefter lagras stränglängden 11 i BX-registret, som används för beräkningsstöd och temporär datalagring. (
MOVL $0Xb, BX) - runtime.printstring(SB) skriver ackumulatorinformationen till SB.
- En tom rad skrivs även till SB med rumtime.printnl(SB).
- Strängbufferten frigörs med runtime.printunlock(SB).
- ADDQ $0x10, SP återställer de 16 byte stackminne som lånats. - Eftersom ingångspunkten ursprungligen angavs genom att placeras på stacken, tas den nu bort från stacken med POPQ BP innan en retursignal ges.
- Därefter anropas runtime.morestack_noctxt.abi0(SB) för att, i enlighet med ett managed language, allokera tillräckligt med stackutrymme och konfigurera runtime-komponenter som GC.
- Exekveringen flyttas till den hanterade adressen main.main(SB).
Som framgår är assemblyn för affärslogiken relativt tydlig och har endast en tunn runtime-hantering tillagd.
Vid avsaknad av optimering
Ovanstående form är resultatet av att Go-kompilatorn automatiskt har utfört inlining av två separata funktioner. För utbildningssyften kommer vi dock i detta fall att förhindra inlining av sayHello.
För att uppnå detta kompileras källkoden med följande flagga:
1 go build -gcflags="-l" main.go
Vid granskning av resultatet i skalet upptäcks redundant assembly-kod.
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 har alltså bekräftats att kompilatorn optimerar bort sådana redundanta operationer, ineffektiv loop-unrolling och liknande.
Nästa gång
Nästa gång kommer vi att behandla if-satser och switch-satser i Go. Om tid finnes kommer vi även att analysera Go-runtime-sektionerna i framtiden.