GoSuda

Qu'est-ce qu'un Managed Language ?

By Lee Yunjin
views ...

Qu'est-ce qu'un langage managé ?

Un langage managé, contrairement à un langage non managé — c'est-à-dire un langage qui s'exécute sans s'écarter significativement de la logique établie par le programmeur — est un langage qui exécute au sein du runtime des fonctionnalités telles que le GC, l'optimisation du runtime, les green threads et la gestion de la concurrence, dispensant ainsi l'utilisateur de procéder à une gestion périlleuse de bas niveau.

Dans le cas de ces langages, bien que l'avantage réside dans la possibilité de se concentrer exclusivement sur la logique métier, il arrive que le programme puisse se comporter différemment de l'intuition du programmeur, nécessitant parfois un réglage fin du runtime.

Pour commencer, nous allons examiner le langage Go, qui, parmi les langages managés, est le plus fidèle à la philosophie minimaliste et dont l'assembly est le plus explicite.

Structure binaire du langage Go

.text.data.gopclntab, .typelink etc.
Code machine à exécuterDonnées à stockerSections du runtime du langage

Étant donné que le langage Go n'effectue pas une traduction machine 1:1 fidèle à l'entrée de l'utilisateur, la logique de la section .text est étroitement liée aux sections du runtime du langage.

De plus, des fonctions que l'utilisateur n'a pas explicitement écrites, telles que runtime.printnl(), sont ajoutées à l'assembly de la section .text. Grâce à cette insertion automatique de code, le langage Go aide le développeur à s'affranchir d'une gestion manuelle.

Observation de la fonction main en Go

Tout d'abord, rédigeons un exemple de code source simple, main.go, et examinons-le depuis main sur une machine AMD64.

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

Ensuite, nous procédons à la compilation comme suit :

1go build main.go

Go prend en charge go tool pour faciliter le débogage de bas niveau. Pour n'afficher que l'assembly correspondant à la fonction main du package main dans go tool, nous saisissons cette commande :

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)
  • Après avoir comparé par CMPQ si le thread actuel est entré, si c'est le cas, il saute au point d'entrée 0x468f95.
  • Le point d'entrée est inséré dans la pile avec PUSHQ BP.
  • Le point de départ de la pile est spécifié au début de la fonction dans le registre SP, où les données ont été chargées le plus récemment, afin de fixer le point d'entrée pour la référence aux variables locales.
  • Ensuite, une pile de 16 octets est réservée pour les variables locales (SUBQ $0x10, SP), et NOPL est utilisé pour remplir plusieurs octets afin d'aligner le cache CPU.
  • Le runtime Go verrouille la sortie du tampon de chaîne en appelant runtime.printlock(SB).
  • L'instruction LEAQ est utilisée pour stocker l'adresse de début de la chaîne allouée dans AX, l'accumulateur utilisé pour le stockage des données parmi les registres à usage général.
  • Ensuite, la longueur de la chaîne, 11, est stockée dans le registre BX, utilisé pour l'assistance aux calculs et le stockage temporaire de données. (MOVL $0Xb, BX)
  • Les informations de l'accumulateur sont envoyées vers SB via runtime.printstring(SB).
  • Un saut de ligne est également écrit vers SB via runtime.printnl(SB).
  • Le tampon de chaîne est libéré par runtime.printunlock(SB).
  • ADDQ $0x10, SP restitue les 16 octets de mémoire de pile empruntés. - Comme le point d'entrée avait été initialement placé sur la pile, on le retire avec POPQ BP avant d'envoyer le signal de retour.
  • Ensuite, runtime.morestack_noctxt.abi0(SB) alloue une pile suffisante, comme il sied à un langage managé, et configure le runtime tel que le GC.
  • Le programme se déplace vers l'adresse gérée main.main(SB).

Comme on peut le constater, l'assembly de la logique métier est assez clair, avec seulement une fine couche de gestion du runtime ajoutée.

En l'absence d'optimisation

La forme ci-dessus est le résultat de l'optimisation automatique par le compilateur Go, qui a effectué l'inlining des deux fonctions initialement séparées. Cependant, pour les besoins de notre apprentissage, nous allons éviter l'inlining de sayHello dans ce cas précis.

Pour ce faire, nous compilons la source avec le flag suivant :

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

En affichant les résultats dans le shell, on découvre un assembly redondant.

 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$

En somme, il est confirmé que ce que le compilateur optimise, ce sont précisément ces opérations redondantes, le déroulement inefficace des boucles, etc.

La prochaine fois

La prochaine fois, nous aborderons les instructions if et switch dans le langage Go. Si le temps le permet, nous analyserons également les sections du runtime Go ultérieurement.