Go'nun büyük boyutta derlenmiş yürütülebilir dosyasının nedeni


91

Linux makinemde yerel yürütülebilir dosya oluşturan bir merhaba dünya Go programı hazırladım. Ama basit Hello world Go programının boyutunu görünce şaşırdım, 1.9MB idi!

Go'da bu kadar basit bir programın çalıştırılabilirliği neden bu kadar büyük?


22
Kocaman? Sanırım o zaman fazla Java yapmıyorsun!
Rick-777

20
Eh, C / C ++ geçmişinden im!
Karthic Rao

Bu scala-native merhaba dünyasını yeni denedim: scala-native.org/en/latest/user/sbt.html#minimal-sbt-project Derlemek, bir çok şeyi indirmek oldukça zaman aldı ve ikili, 3.9. MB.
bli

Aşağıdaki cevabımı 2019 bulguları ile güncelledim .
VonC

1
C # .NET Core 3.1'deki basit Hello World uygulaması dotnet publish -r win-x64 -p:publishsinglefile=true -p:publishreadytorun=true -p:publishtrimmed=trueyaklaşık 26MB civarında bir ikili dosya oluşturur!
Jalal

Yanıtlar:


91

Tam olarak bu soru resmi SSS'de yer almaktadır: Benim önemsiz programım neden bu kadar büyük bir ikili dosyadır?

Cevabın alıntılanması:

Gc araç zinciri (bağlayıcıların 5l, 6lve 8l) statik bağlama yapmak. Bu nedenle tüm Go ikili dosyaları, dinamik tür kontrollerini, yansımayı ve hatta panik zamanı yığın izlerini desteklemek için gerekli çalışma zamanı tür bilgileriyle birlikte Go çalışma zamanını içerir.

Linux'ta gcc kullanılarak statik olarak derlenen ve bağlanan basit bir C "merhaba, dünya" programı printf,. Eşdeğer bir Go programı kullanan fmt.Printfyaklaşık 1,9 MB'dir, ancak bu daha güçlü çalışma zamanı desteği ve tür bilgileri içerir.

Dolayısıyla, Hello World'ünüzün yerel yürütülebilir dosyası 1,9 MB'dir, çünkü çöp toplama, yansıtma ve diğer birçok özelliği (programınızın gerçekten kullanmayabileceği, ancak orada bulunan) sağlayan bir çalışma zamanı içerir. Ve metni fmtyazdırmak için kullandığınız paketin uygulaması "Hello World"(artı bağımlılıkları).

Şimdi şunu deneyin: fmt.Println("Hello World! Again")programınıza başka bir satır ekleyin ve yeniden derleyin. Sonuç 2x 1.9MB değil, yine de sadece 1.9 MB olacak! Evet, çünkü kullanılan tüm kitaplıklar ( fmtve bağımlılıkları) ve çalışma zamanı zaten çalıştırılabilir dosyaya eklenmiştir (ve bu nedenle, eklediğiniz 2. metni yazdırmak için yalnızca birkaç bayt daha eklenecektir).


12
Glibc ile statik olarak bağlantılı AC "merhaba dünya" programı 750K'dır, çünkü glibc açıkça statik bağlantı için tasarlanmamıştır ve hatta bazı durumlarda düzgün bir şekilde statik bağlantı kurulması imkansızdır. Musl libc ile statik olarak bağlantılı bir "merhaba dünya" programı 14K'dır.
Craig Barnes

Yine de bakıyorum, ancak neyin bağlantılı olduğunu bilmek güzel olurdu, böylece bir saldırgan kötü kodla bağlantı kurmuyor olabilir.
Richard

Öyleyse neden Go çalışma zamanı kitaplığı bir DLL dosyasında değil, böylece tüm Go exe dosyaları arasında paylaşılabilir? O zaman bir "merhaba dünya" programı beklendiği gibi 2 MB yerine birkaç KB olabilir. Her programda tüm çalışma zamanı kitaplığına sahip olmak, Windows'ta MSVC'nin başka türlü harika bir alternatifi için ölümcül bir kusurdur.
David Spector

Yorumuma bir itiraz beklemem daha iyi: Go "statik olarak bağlantılı". Tamam, o zaman DLL yok. Ancak statik bağlama, tüm bir kitaplığa bağlanmanız (bağlamanız) gerektiği anlamına gelmez, yalnızca kitaplıkta gerçekten kullanılan işlevler!
David Spector

44

Aşağıdaki programı düşünün:

package main

import "fmt"

func main() {
    fmt.Println("Hello World!")
}

Bunu Linux AMD64 makinemde (Go 1.9) şöyle derlersem:

$ go build
$ ls -la helloworld
-rwxr-xr-x 1 janf group 2029206 Sep 11 16:58 helloworld

Yaklaşık 2 Mb boyutunda bir ikili dosya alıyorum.

Bunun nedeni (diğer cevaplarda açıklanmıştır) oldukça büyük olan "fmt" paketini kullanmamızdır, ancak ikili de çıkarılmamıştır ve bu, sembol tablosunun hala orada olduğu anlamına gelir. Bunun yerine derleyiciye ikiliyi çıkarması talimatını verirsek, çok daha küçük olacaktır:

$ go build -ldflags "-s -w"
$ ls -la helloworld
-rwxr-xr-x 1 janf group 1323616 Sep 11 17:01 helloworld

Ancak, programı fmt.Println yerine yazdırma yerleşik işlevini kullanacak şekilde yeniden yazarsak, şöyle:

package main

func main() {
    print("Hello World!\n")
}

Ve sonra derleyin:

$ go build -ldflags "-s -w"
$ ls -la helloworld
-rwxr-xr-x 1 janf group 714176 Sep 11 17:06 helloworld

Daha da küçük bir ikili ile sonuçlanırız. Bu, UPX paketleme gibi hilelere başvurmadan elde edebileceğimiz kadar küçüktür, bu nedenle Go çalışma zamanının ek yükü kabaca 700 Kb'dir.


4
UPX, ikili dosyaları sıkıştırır ve çalıştırıldıklarında onları anında açar. Bazı senaryolarda faydalı olabileceğinden, ne yaptığını açıklamadan bunu bir numara olarak görmezden gelmezdim. İkili boyut, başlatma süresi ve RAM kullanımı pahasına bir şekilde azaltılır; dahası, performans da biraz etkilenebilir. Bir örnek olarak, bir yürütülebilir dosya (çıkarılmış) boyutunun% 30'una küçültülebilir ve çalışması 35 ms daha uzun sürebilir.
simlev

10

Golang / go projesindeki ikili boyut sorununun 6853 numaralı sayı ile izlendiğini unutmayın .

Örneğin, a26c01a (Go 1.4 için) işle, merhaba dünyayı 70 KB azaltın :

çünkü bu isimleri sembol tablosuna yazmıyoruz.

1.5 için derleyici, derleyici, bağlayıcı ve çalışma zamanının tamamen Go'da olacağı düşünüldüğünde, daha fazla optimizasyon bekleyebilirsiniz.


2016 Go 1.7 Güncellemesi: bu optimize edilmiştir: bkz. " Smaller Go 1.7 ikili dosyaları ".

Ancak bu gün (Nisan 2019) en çok yer alan şey runtime.pclntab.
Bkz: " Go büyük? Boyut görselleştirme D3 kullanarak yürütülebilir dosyalar yüzden neden Git çalıştırılabilir dosyalardır gelen" Raphael 'Kena' Poss .

Çok iyi belgelenmemiş ancak Go kaynak kodundan gelen bu yorum amacını gösteriyor:

// A LineTable is a data structure mapping program counters to line numbers.

Bu veri yapısının amacı, Go çalışma zamanı sisteminin bir çökme durumunda veya runtime.GetStackAPI aracılığıyla dahili isteklerde açıklayıcı yığın izleri üretmesini sağlamaktır .

Bu yüzden faydalı görünüyor. Ama neden bu kadar büyük?

Yukarıda bağlantısı verilen kaynak dosyada gizlenen URL https://golang.org/s/go12symtab Go 1.0 ve 1.2 arasında ne olduğunu açıklayan bir belgeye yönlendirir. Kelimeleri ifade etmek:

1.2'den önce, Go bağlayıcı sıkıştırılmış bir çizgi tablosu yayıyordu ve program, çalıştırma zamanında başlatmanın ardından onu açardı.

Go 1.2'de, çalıştırılabilir dosyadaki satır tablosunu, ek bir açma adımı olmadan çalışma zamanında doğrudan kullanıma uygun nihai biçimine önceden genişletme kararı verildi.

Başka bir deyişle, Go ekibi, başlatma süresinden tasarruf etmek için yürütülebilir dosyaları daha büyük hale getirmeye karar verdi.

Ayrıca, veri yapısına bakıldığında, derlenmiş ikili dosyalardaki toplam boyutunun, her bir işlevin ne kadar büyük olduğuna ek olarak, programdaki işlev sayısında süper doğrusal olduğu görülmektedir.

https://science.raphael.poss.name/go-executable-size-visualization-with-d3/size-demo-ss.png


2
Uygulama dilinin bununla ne ilgisi olduğunu anlamıyorum. Paylaşılan kitaplıkları kullanmaları gerekir. Zaten bu gün ve çağda olmamaları biraz inanılmaz.
user207421

3
@EJP: Neden paylaşılan kitaplıkları kullanmaları gerekiyor?
Flimzy

10
@EJP, Go'nun basitliğinin bir parçası, paylaşılan kitaplıkları kullanmamaktır. Aslında Go'nun hiçbir bağımlılığı yoktur, düz sistem çağrıları kullanır. Sadece tek bir ikili dağıtın ve işe yarıyor. Aksi takdirde, dile ve ekosisteme önemli ölçüde zarar verirdi.
creker

11
Statik olarak bağlantılı ikili dosyalara sahip olmanın sıklıkla unutulan bir yönü, onları tamamen boş bir Docker konteynerinde çalıştırmayı mümkün kılmasıdır. Güvenlik açısından bu idealdir. Konteyner boş olduğunda, içeri girebilirsiniz (statik olarak bağlı ikili dosyanın kusurları varsa), ancak konteynerde bulunacak hiçbir şey olmadığından, saldırı orada durur.
Joppe
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.