プログラミングや低レイヤで遊ぶ人

基本的には遊んだことを記事にしていく

golangのFAQを見たら面白かったよって話 その1

golang FAQ

golang blog ついでにgolang FAQ見ていた時の話

Why is my trivial program such a large binary?


golangの公式にFAQがあったので眺めていたら以下の質問がありました。

Why is my trivial program such a large binary?


要はhello, worldみたいな小さいコードなのになんでビルドするとでかい実行ファイルになるのかということらしいです。

え?そなの?。。。。CPU使用率とかメモリ量とかしか見てなかったので知らんかった。

というわけで他言語と比較してみました。

・ windows10 home
gcc (x86_64-posix-seh-rev0, Built by MinGW-W64 project) 8.1.0
・ javac 12.0.1
・ go version go1.13.8 windows/amd64

以下は各言語のコードと実行したコマンドです。ただのhello, worldなので見なくても問題ないです。

//binary.c
#include<stdio.h>

int main(){
    printf("hello, world\n");
    return 0;
}
//gcc exec cmd
gcc -O3 binary.c
//Binary.java
class Binary {
    public static void main(String[] args) {
    System.out.println("Hello, world.");
    }
}
//javac exec cmd
javac Binary.java
//binary.go
package main

import "fmt"

func main(){
    fmt.Print("hello, world\n")
}
//go exec cmd
go build binary.go

そんでもって生成されたバイナリのサイズが以下のものです。


gcc   : 54,058    byte
javac : 419       byte
go    : 2,161,115 byte



比較するとかなりデカかった


なんでや...

なぜバイナリがでかくなるのか

FAQなのですぐ下に答えは書いてありました。以下はFAQの引用です。

The linker in the gc toolchain creates statically-linked binaries by default. All Go binaries therefore include the Go runtime, along with the run-time type information necessary to support dynamic type checks, reflection, and even panic-time stack traces.

A simple C "hello, world" program compiled and linked statically using gcc on Linux is around 750 kB, including an implementation of printf. An equivalent Go program using fmt.Printf weighs a couple of megabytes, but that includes more powerful run-time support and type and debugging information.
                    ・
                    ・
                    ・



つまり、デバッグ情報があるので重くなっているらしい。


-ldflags=-w のオプションをつけるとDWARFの生成がなくなり軽くなるらしい。とりあえずやってみました。-ldflags=-w のオプションをつけただけです。

go build -ldflags=-w .\binary.go
noflag.exe : 2,106,368 byte
flag.exe   : 1,653,248 byte

確かにだいぶ下がっていますね。


でも、DWAEFとは......?

DWAEFとは



どうやらデバッグ情報の形式らしい

しかし、私の環境はwindowsなのでPDBという形式になるらしい。
以下のもコマンドでデバッグ環境を入れられるらしいです。

> go get -u github.com/derekparker/delve/cmd/dlv



私の環境では以下のようになりました。

> dlv debug binary.go
Type 'help' for list of commands.
(dlv) b main.main
Breakpoint 1 set at 0x4bb156 for main.main() C:/Users/ttnmr/HOME/tmp/binary.go:5
(dlv) c
> main.main() C:/Users/ttnmr/HOME/tmp/binary.go:5 (hits goroutine(1):1 total:1) (PC: 0x4bb156)
     1: package main
     2:
     3: import "fmt"
     4:
=>   5: func main(){
     6:         fmt.Print("hello, world\n")
     7: }
(dlv)



gdbなどと使い方は同じのようです。これでfmt.printなどでデバッグしなくてもスマートにできそう。

参考

golang FAQ
デバッグ情報の歩き方
Microsoft、PDBフォーマットの情報をGitHubで公開
はじめてのgolang デバッグ&テストコード