Motivo per l’enorme dimensione dell’eseguibile compilato di Go

Ho eseguito un programma Hello World che ha generato un eseguibile nativo sulla mia macchina Linux. Ma sono stato sorpreso di vedere la dimensione del semplice programma Hello world Go, era 1.9 MB!

Perché l’eseguibile di un programma così semplice in Go è così grande?

Questa domanda esatta appare nelle FAQ ufficiali: perché il mio programma banale è un così grande binario?

Citando la risposta:

I linker nella catena di strumenti gc ( 5l , 6l e 8l ) eseguono collegamenti statici. I file binari All Go includono quindi il tempo di esecuzione Go, insieme alle informazioni sul tipo di esecuzione necessarie per supportare i controlli di tipo dinamico, la riflessione e persino le tracce dello stack di panico.

Un semplice programma C “hello, world” compilato e collegato staticamente tramite gcc su Linux è di circa 750 kB, inclusa un’implementazione di printf . Un programma Go equivalente che utilizza fmt.Printf è di circa 1,9 MB, ma include un supporto più potente in fase di esecuzione e informazioni sul tipo.

Quindi l’eseguibile nativo di Hello World è di 1,9 MB perché contiene un runtime che fornisce garbage collection, reflection e molte altre funzionalità (che il tuo programma potrebbe non usare davvero, ma è lì). E l’implementazione del pacchetto fmt che hai usato per stampare il testo "Hello World" (più le sue dipendenze).

Ora prova quanto segue: aggiungi un’altra fmt.Println("Hello World! Again") al tuo programma e compila nuovamente. Il risultato non sarà 2x 1.9 MB, ma solo 1.9 MB! Sì, perché tutte le librerie utilizzate ( fmt e le sue dipendenze) e il runtime sono già stati aggiunti all’eseguibile (e quindi verranno aggiunti solo pochi byte per stampare il secondo testo che hai appena aggiunto).

Considera il seguente programma:

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

Se lo costruisco sulla mia macchina Linux AMD64 (Vai 1.9), in questo modo:

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

Ottengo un binario che ha una dimensione di circa 2 Mb.

La ragione di ciò (che è stata spiegata in altre risposte) è che stiamo usando il pacchetto “fmt” che è abbastanza grande, ma anche il binario non è stato rimosso e questo significa che la tabella dei simboli è ancora lì. Se invece istruiamo il compilatore a rimuovere il binario, diventerà molto più piccolo:

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

Tuttavia, se riscriviamo il programma per usare la funzione builtin print, invece di fmt.Println, in questo modo:

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

E poi compilarlo:

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

Finiamo con un binario ancora più piccolo. Questo è il minimo che possiamo ottenere senza ricorrere a trucchi come UPX-packing, quindi il sovraccarico di Go-runtime è di circa 700 Kb.

Si noti che il problema delle dimensioni binarie è tracciato dal problema 6853 nel progetto golang / go .

Ad esempio, commit a26c01a (per Go 1.4) cut hello world di 70kB :

perché non scriviamo quei nomi nella tabella dei simboli.

Considerando che compilatore, assemblatore, linker e runtime per 1.5 saranno interamente in Go, ci si può aspettare un’ulteriore ottimizzazione.