GoSuda

Čo je to Managed Language?

By Lee Yunjin
views ...

Čo je to managed jazyk?

Managed jazyk je jazyk, ktorý na rozdiel od unmanaged jazykov – teda takých, ktoré vykonávajú iba logiku napísanú programátorom bez výrazných zásahov – spúšťa počas runtime procesy, ako sú GC, runtime optimalizácia, green threads a spracovanie súbežnosti, čím zbavuje používateľa nutnosti vykonávať rizikovú správu na nízkej úrovni.

V prípade takýchto jazykov je výhodou možnosť plne sa sústrediť na vývoj a biznis logiku, avšak na druhej strane môže program v skutočnosti fungovať inak, než ako napovedá intuícia programátora, čo niekedy vyžaduje precízne runtime tuning.

Najprv sa pozrieme na jazyk Go, ktorý je spomedzi managed jazykov najvernejší minimalistickej filozofii a ktorého assembly je priamočiare.

Binárna štruktúra jazyka Go

.text.data.gopclntab, .typelink atď.
Strojový kód na vykonanieUložené dátaRuntime sekcie jazyka

Keďže jazyk Go nevykonáva preklad do strojového kódu v pomere 1:1 podľa vstupu používateľa, logika v sekcii .text úzko súvisí s runtime sekciami jazyka.

Okrem toho sa do assembly v sekcii .text pridávajú funkcie, ktoré používateľ explicitne nenapísal, ako napríklad rumtime.printnl(). Prostredníctvom tohto automatického vkladania kódu pomáha jazyk Go vývojárom oslobodiť sa od manuálnej správy.

Zobrazenie iba funkcie main v jazyku Go

Najprv si vytvorme jednoduchý vzorový zdrojový kód main.go a pozrime sa na main na stroji AMD64.

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

Následne vykonáme zostavenie týmto spôsobom:

1go build main.go

Go podporuje go tool pre jednoduchšie ladenie na nízkej úrovni. Aby sme v go tool videli assembly iba pre main funkciu v main balíku, zadáme tento príkaz:

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)
  • Po porovnaní pomocou CMPQ, či sme vstúpili do aktuálneho vlákna, sa v prípade zhody skáče na Entrypoint 0x468f95.
  • Vstupný bod sa vloží do zásobníka pomocou PUSHQ BP.
  • V registri SP, kde boli naposledy uložené dáta, sa pri štarte funkcie určí začiatok zásobníka, čím sa zafixuje vstupný bod pre referencovanie lokálnych premenných.
  • Následne sa vyhradí 16 bajtov zásobníka pre lokálne premenné (SUBQ $0x10, SP) a pomocou NOPL sa vyplní niekoľko bajtov na zarovnanie CPU cache.
  • Go Runtime uzamkne výstup string buffera volaním runtime.printlock(SB).
  • Pomocou inštrukcie LEAQ sa do akumulátora AX, ktorý sa používa na ukladanie dát, uloží začiatočná adresa alokovaného reťazca.
  • Následne sa do registra BX, ktorý sa používa na pomocné výpočty a dočasné ukladanie dát, uloží dĺžka reťazca 11. (MOVL $0Xb, BX)
  • Pomocou runtime.printstring(SB) sa informácie z akumulátora vypíšu smerom k SB.
  • Prázdny riadok sa takisto zapíše k SB pomocou rumtime.printnl(SB).
  • String buffer sa uvoľní pomocou runtime.printunlock(SB).
  • Pomocou ADDQ $0x10, SP sa vráti požičaných 16 bajtov pamäte zásobníka. Keďže sme vstupný bod na začiatku vložili do zásobníka, teraz ho pomocou POPQ BP odstránime a odošleme signál návratu.
  • Následne sa pomocou runtime.morestack_noctxt.abi0(SB), ako sa na managed jazyk patrí, alokuje dostatočný zásobník a nastaví sa runtime, ako napríklad GC.
  • Presunieme sa na spravovanú adresu main.main(SB).

Ako vidíme, assembly biznis logiky je pomerne jasné a je doplnené len o ľahkú správu runtime.

Bez optimalizácie

Vyššie uvedená forma je výsledkom automatického inlining optimalizácie dvoch oddelených funkcií kompilátorom Go. Pre účely štúdia však v tomto prípade zakážeme inlining funkcie sayHello.

Dosiahneme to kompiláciou zdrojového kódu s nasledujúcim príznakom:

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

Ak si výsledok zobrazíme v shelli, zistíme, že sa vyskytuje duplicitné assembly.

 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$

Potvrdilo sa teda, že kompilátor optimalizuje práve takéto duplicitné operácie, neefektívne rozvinutia slučiek (loop unrolling) a podobne.

Nabudúce

Nabudúce sa budeme venovať if a switch podmienkam v jazyku Go. Ak budem mať neskôr čas, budem analyzovať aj runtime sekcie jazyka Go.