Linuxマシンでネイティブ実行可能ファイルを生成するHelloWorldGoプログラムをコンパイルしました。しかし、単純なHello worldGoプログラムのサイズが1.9MBであることに驚きました。
Goでこのような単純なプログラムの実行可能ファイルが非常に大きいのはなぜですか?
Linuxマシンでネイティブ実行可能ファイルを生成するHelloWorldGoプログラムをコンパイルしました。しかし、単純なHello worldGoプログラムのサイズが1.9MBであることに驚きました。
Goでこのような単純なプログラムの実行可能ファイルが非常に大きいのはなぜですか?
dotnet publish -r win-x64 -p:publishsinglefile=true -p:publishreadytorun=true -p:publishtrimmed=true
Core3.1のシンプルなHelloWorldアプリは、約26MBのバイナリファイルを生成します。
回答:
この正確な質問は公式FAQに表示されます:なぜ私の些細なプログラムがこんなに大きなバイナリなのですか?
答えを引用する:
GCツールチェーンのリンカー(
5l
、6l
、とは8l
)静的リンクを行います。したがって、すべてのGoバイナリには、動的型チェック、リフレクション、さらにはパニック時のスタックトレースをサポートするために必要な実行時型情報とともに、Goランタイムが含まれています。Linuxでgccを使用して静的にコンパイルおよびリンクされた単純なCの「hello、world」プログラムは、の実装を含めて約750kBです
printf
。使用する同等のGoプログラムfmt.Printf
は約1.9MBですが、これにはより強力なランタイムサポートとタイプ情報が含まれています。
つまり、HelloWorldのネイティブ実行可能ファイルは1.9MBです。これは、ガベージコレクション、リフレクション、およびその他の多くの機能を提供するランタイムが含まれているためです(プログラムでは実際には使用されないかもしれませんが、そこにあります)。そして、テキスト(およびその依存関係)fmt
を印刷するために使用したパッケージの実装"Hello World"
。
次に、次のことを試しfmt.Println("Hello World! Again")
てください。プログラムに別の行を追加して、再度コンパイルします。結果は2x1.9MBにはなりませんが、それでも1.9MBになります。はい。使用されているすべてのライブラリ(fmt
およびその依存関係)とランタイムが実行可能ファイルにすでに追加されているためです(したがって、追加した2番目のテキストを出力するためにさらに数バイトが追加されます)。
次のプログラムを検討してください。
package main
import "fmt"
func main() {
fmt.Println("Hello World!")
}
Linux AMD64マシン(Go 1.9)でこれをビルドすると、次のようになります。
$ go build
$ ls -la helloworld
-rwxr-xr-x 1 janf group 2029206 Sep 11 16:58 helloworld
サイズが約2Mbのバイナリを取得します。
この理由(他の回答で説明されています)は、非常に大きい「fmt」パッケージを使用しているが、バイナリも削除されておらず、シンボルテーブルがまだ存在していることを意味します。代わりに、バイナリを削除するようコンパイラに指示すると、バイナリははるかに小さくなります。
$ go build -ldflags "-s -w"
$ ls -la helloworld
-rwxr-xr-x 1 janf group 1323616 Sep 11 17:01 helloworld
ただし、次のように、fmt.Printlnではなく組み込み関数printを使用するようにプログラムを書き直すと、次のようになります。
package main
func main() {
print("Hello World!\n")
}
そしてそれをコンパイルします:
$ go build -ldflags "-s -w"
$ ls -la helloworld
-rwxr-xr-x 1 janf group 714176 Sep 11 17:06 helloworld
最終的にはさらに小さなバイナリになります。これは、UPXパッキングのようなトリックに頼らずに取得できる限り小さいため、Goランタイムのオーバーヘッドは約700Kbです。
バイナリサイズの問題は、golang / goプロジェクトの問題6853によって追跡されていることに注意してください。
たとえば、コミットa26c01a(Go 1.4の場合)はhelloworldを70kBカットします。
これらの名前をシンボルテーブルに書き込まないためです。
1.5のコンパイラ、アセンブラ、リンカ、およびランタイムが完全にGoに含まれることを考えると、さらなる最適化が期待できます。
2016 Go 1.7の更新:これは最適化されています:「 SmallerGo1.7バイナリ」を。
しかし、最近(2019年4月)、最も行われているのはruntime.pclntab
です。
「を参照してくださいゴーの大?サイズの可視化がD3を使用して実行可能ファイル、なぜ私のゴー実行可能ファイルですから」ラファエル「kena」ポス。
あまり文書化されていませんが、Goソースコードからのこのコメントはその目的を示唆しています。
// A LineTable is a data structure mapping program counters to line numbers.
このデータ構造の目的は、Goランタイムシステムがクラッシュ時または
runtime.GetStack
APIを介した内部リクエスト時に記述的なスタックトレースを生成できるようにすることです。だからそれは便利なようです。しかし、なぜそれがそんなに大きいのですか?
URL https://golang.org/s/go12symtabゴー1.0と1.2の間で何が起こったのかを説明した文書にaforelinkedソースファイルのリダイレクトに隠されました。言い換えると:
1.2より前は、Goリンカは圧縮された行テーブルを発行していましたが、プログラムは実行時の初期化時にそれを解凍していました。
Go 1.2では、実行可能ファイルの行テーブルを、追加の解凍手順なしで、実行時に直接使用するのに適した最終形式に事前拡張することが決定されました。
つまり、Goチームは、初期化時間を節約するために、実行可能ファイルを大きくすることにしました。
また、データ構造を見ると、コンパイルされたバイナリの全体的なサイズは、各関数の大きさに加えて、プログラム内の関数の数において超線形であるように見えます。