Saya mematuhi program hello world Go yang menghasilkan executable asli di mesin linux saya. Tapi saya terkejut melihat ukuran program Hello world Go yang sederhana, yaitu 1.9MB!
Mengapa program yang bisa dieksekusi di Go begitu besar?
Saya mematuhi program hello world Go yang menghasilkan executable asli di mesin linux saya. Tapi saya terkejut melihat ukuran program Hello world Go yang sederhana, yaitu 1.9MB!
Mengapa program yang bisa dieksekusi di Go begitu besar?
dotnet publish -r win-x64 -p:publishsinglefile=true -p:publishreadytorun=true -p:publishtrimmed=true
menghasilkan file biner sekitar ~ 26MB!
Jawaban:
Pertanyaan persis ini muncul di FAQ resmi: Mengapa program sepele saya memiliki biner yang begitu besar?
Mengutip jawabannya:
The linker dalam rantai alat gc (
5l
,6l
, dan8l
) melakukan menghubungkan statis. Oleh karena itu, semua biner Go menyertakan run-time Go, bersama dengan informasi jenis run-time yang diperlukan untuk mendukung pemeriksaan tipe dinamis, refleksi, dan bahkan pelacakan tumpukan waktu panik.Program C "hello, world" sederhana yang dikompilasi dan ditautkan secara statis menggunakan gcc di Linux berukuran sekitar 750 kB, termasuk implementasi
printf
. Program Go yang setara menggunakanfmt.Printf
sekitar 1,9 MB, tetapi itu termasuk dukungan run-time yang lebih kuat dan informasi jenis.
Jadi executable asli Hello World Anda adalah 1,9 MB karena berisi runtime yang menyediakan pengumpulan sampah, refleksi, dan banyak fitur lainnya (yang mungkin tidak benar-benar digunakan oleh program Anda, tetapi program itu ada di sana). Dan implementasi fmt
paket yang Anda gunakan untuk mencetak "Hello World"
teks (ditambah dependensinya).
Sekarang coba yang berikut ini: tambahkan fmt.Println("Hello World! Again")
baris lain ke program Anda dan kompilasi lagi. Hasilnya bukan 2x 1.9MB, tapi tetap hanya 1.9 MB! Ya, karena semua pustaka yang digunakan ( fmt
dan dependensinya) dan runtime sudah ditambahkan ke file yang dapat dieksekusi (sehingga hanya beberapa byte lagi yang akan ditambahkan untuk mencetak teks ke-2 yang baru saja Anda tambahkan).
Pertimbangkan program berikut:
package main
import "fmt"
func main() {
fmt.Println("Hello World!")
}
Jika saya membangun ini di mesin Linux AMD64 saya (Go 1.9), seperti ini:
$ go build
$ ls -la helloworld
-rwxr-xr-x 1 janf group 2029206 Sep 11 16:58 helloworld
Saya mendapatkan biner berukuran sekitar 2 Mb.
Alasan untuk ini (yang telah dijelaskan di jawaban lain) adalah karena kami menggunakan paket "fmt" yang cukup besar, tetapi binernya juga belum dihapus dan ini berarti tabel simbol masih ada. Jika kita menginstruksikan kompilator untuk menghapus biner, itu akan menjadi jauh lebih kecil:
$ go build -ldflags "-s -w"
$ ls -la helloworld
-rwxr-xr-x 1 janf group 1323616 Sep 11 17:01 helloworld
Namun, jika kita menulis ulang program untuk menggunakan fungsi cetak bawaan, daripada fmt.Println, seperti ini:
package main
func main() {
print("Hello World!\n")
}
Dan kemudian kompilasi:
$ go build -ldflags "-s -w"
$ ls -la helloworld
-rwxr-xr-x 1 janf group 714176 Sep 11 17:06 helloworld
Kami berakhir dengan biner yang lebih kecil. Ini sekecil yang bisa kita dapatkan tanpa menggunakan trik seperti pengemasan UPX, jadi overhead waktu proses Go kira-kira 700 Kb.
Perhatikan bahwa masalah ukuran biner dilacak oleh masalah 6853 di proyek golang / go .
Misalnya, lakukan a26c01a (untuk Go 1.4) potong hello world sebesar 70kB :
karena kami tidak menulis nama-nama itu ke dalam tabel simbol.
Mengingat compiler, assembler, linker, dan runtime untuk 1.5 akan sepenuhnya ada di Go, Anda dapat mengharapkan pengoptimalan lebih lanjut.
Update 2016 Go 1.7: ini telah dioptimalkan: lihat " Smaller Go 1.7 binaries ".
Tapi hari ini (April 2019), yang paling banyak terjadi adalah runtime.pclntab
.
Lihat " Mengapa file Go saya yang dapat dieksekusi begitu besar? Visualisasi ukuran file yang dapat dieksekusi Go menggunakan D3 " dari Raphael 'kena' Poss .
Ini tidak terlalu terdokumentasi dengan baik namun komentar dari kode sumber Go ini menyarankan tujuannya:
// A LineTable is a data structure mapping program counters to line numbers.
Tujuan dari struktur data ini adalah untuk memungkinkan sistem waktu proses Go menghasilkan jejak tumpukan deskriptif saat terjadi kerusakan atau atas permintaan internal melalui
runtime.GetStack
API.Jadi sepertinya berguna. Tapi kenapa begitu besar?
URL https://golang.org/s/go12symtab yang disembunyikan di file sumber yang ditautkan sebelumnya dialihkan ke dokumen yang menjelaskan apa yang terjadi antara Go 1.0 dan 1.2. Untuk memparafrasekan:
sebelum 1.2, linker Go memancarkan tabel baris terkompresi, dan program akan mendekompresi saat inisialisasi pada saat run-time.
di Go 1.2, keputusan dibuat untuk memperluas tabel baris di file yang dapat dieksekusi ke dalam format akhirnya yang sesuai untuk digunakan langsung pada waktu proses, tanpa langkah dekompresi tambahan.
Dengan kata lain, tim Go memutuskan untuk memperbesar file yang dapat dieksekusi untuk menghemat waktu inisialisasi.
Juga, melihat struktur datanya, tampak bahwa ukuran keseluruhannya dalam biner yang dikompilasi adalah super linier dalam jumlah fungsi dalam program, selain seberapa besar setiap fungsi.