GoSuda

マネージド言語とは何か?

By Lee Yunjin
views ...

マネージド言語とは何か?

マネージド言語とは、アンマネージド言語、すなわちプログラマーが記述したロジックから大きく逸脱することなく実行のみを行う言語とは異なり、GC、ランタイム最適化、グリーンスレッド、並行処理などをランタイムで実行することで、ユーザーが危険な低レベル管理を行う必要をなくす言語である。

このような言語の場合、ビジネスロジックにのみ集中して開発に没頭できるという利点がある一方で、プログラマーの直感と実際のプログラムの動作が異なる場合があり、精密なランタイムチューニングが必要となることもある。

まず、マネージド言語の中で最もミニマリスト哲学に忠実であり、アセンブリが素直なGo言語について見ていくことにする。

Go言語のバイナリ構造

.text.data.gopclntab, .typelink など
実行される機械語コード保存されるデータ言語ランタイムセクション

Go言語はユーザーが入力した通りに1:1で機械語翻訳を行うわけではないため、.textセクションのロジックは言語ランタイムセクションとも密接に関連している。

また、ユーザーが個別に記述していないruntime.printnl()のような関数が.textセクションのアセンブリに追加される。このような自動的なコード挿入を通じて、Go言語は開発者が手動管理から解放されるよう支援する。

Goにおけるmain関数の部分的な確認

まず、簡単なサンプルソース main.go を作成し、main関数から AMD64マシン上で 確認することにする。

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

その後、以下のようにビルドする。

1go build main.go

Goは容易な低レベルデバッグのために go tool をサポートしている。go tool において、メインパッケージ内のメイン関数のみのアセンブリを確認するために、以下の構文を入力する。

1go tool objdump -s "main\.main" ./main

アセンブリ

 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)
  • 現在のスレッドに進入したかをCMPQで比較した後、条件を満たせばEntrypoint 0x468f95へジャンプする。
  • 進入点を PUSHQ BP でスタックに挿入する。
  • 最も最近にデータが積載されたレジスタSPに、関数開始時のスタック開始地点を指定して、ローカル変数参照時の進入点を固定する。
  • その後16バイト分のローカル変数スタックを予約し (SUBQ $0x10, SP)、NOPLを用いて数バイトを埋めることでCPUキャッシュのアライメントを行う。
  • Go Runtimeにおいて文字列バッファの出力ロックを runtime.printlock(SB) を呼び出してかける。
  • LEAQ命令を使用して、割り当てられた文字列の開始アドレスを汎用レジスタのうちデータ保存に使用するアキュムレータアドレスのAXに保存する。
  • その後、演算補助および一時データ保存に使用するBXレジスタに文字列長11を保存する (MOVL $0xb, BX)。
  • runtime.printstring(SB)によりSB側へアキュムレータ情報を出力する。
  • 一行の空行もruntime.printnl(SB)によりSB側へ書き込む。
  • 文字列バッファをruntime.printunlock(SB)で解放する。
  • ADDQ $0x10, SPで借りた16バイトのスタックメモリを返還する。 - 最初に進入点をスタックに入れて通知したため、ここでPOPQ BPによりスタックから進入点を取り除いてから返還シグナルを出す。
  • その後、runtime.morestack_noctxt.abi0(SB)により、マネージド言語らしく十分なスタックを割り当て、GCなどのランタイムをセットアップする。
  • 管理されたmain.main(SB)アドレスへ移動する。

ご覧の通り、ビジネスロジックのアセンブリは非常に明確であり、軽量なランタイム管理のみが付加された形態となっている。

最適化が行われない場合

上記の形態は、Goコンパイラが本来分離されている2つの関数を自動的にインライン化して最適化した結果である。しかし、学習のために今回は sayHello をインライン化しないようにする。

これを行うために、次のフラグを使用してソースをコンパイルする。

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

シェルで結果を出力すると、重複するアセンブリが発見される。

 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$

すなわち、コンパイラが最適化しているのは、このような重複演算や非効率的なループアンローリングなどが対象であることが確認された。

次回について

次回はGo言語におけるif文、switch文について扱う予定である。今後時間が取れれば、Goランタイムセクションについても分析する予定である。