Motivo dell'enorme dimensione dell'eseguibile compilato di Go


91

Ho seguito un programma Hello World Go che generava eseguibili nativi sulla mia macchina Linux. Ma sono rimasto sorpreso di vedere le dimensioni del semplice programma Hello world Go, 1,9 MB!

Perché l'eseguibile di un programma così semplice in Go è così enorme?


22
Enorme? Immagino che allora non fai molto Java!
Rick-777

20
Bene, vengo dallo sfondo C / C ++!
Karthic Rao

Ho appena provato questo hello world nativo di scala: scala-native.org/en/latest/user/sbt.html#minimal-sbt-project Ci è voluto un po 'di tempo per compilare, scaricare un sacco di cose, e il binario è 3.9 MB.
bli

Ho aggiornato la mia risposta di seguito con i risultati del 2019.
VonC

1
La semplice app Hello World in C # .NET Core 3.1 con dotnet publish -r win-x64 -p:publishsinglefile=true -p:publishreadytorun=true -p:publishtrimmed=truegenera un file binario di circa 26 MB!
Jalal

Risposte:


91

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

Citando la risposta:

I linker nella catena strumento gc ( 5l, 6l, e 8l) fanno il collegamento statico. Tutti i file binari di Go includono quindi il runtime di Go, insieme alle informazioni sul tipo di runtime necessarie per supportare i controlli dinamici del tipo, la riflessione e persino le analisi dello stack in fase di panico.

Un semplice programma C "hello, world" compilato e collegato staticamente usando 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 di runtime più potente e informazioni sul tipo.

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

Ora prova quanto segue: aggiungi un'altra fmt.Println("Hello World! Again")riga al tuo programma e compila di nuovo. Il risultato non sarà 2x 1,9 MB, ma solo 1,9 MB! Sì, perché tutte le librerie utilizzate ( fmte le sue dipendenze) e il runtime sono già aggiunti all'eseguibile (e quindi verranno aggiunti solo pochi byte in più per stampare il 2 ° testo che hai appena aggiunto).


11
Il programma AC "hello world", collegato staticamente con glibc è 750K perché glibc non è espressamente progettato per il collegamento statico ed è persino impossibile collegarlo correttamente in alcuni casi. Un programma "hello world" collegato staticamente con musl libc è 14K.
Craig Barnes

Sto ancora cercando, tuttavia, sarebbe bello sapere cosa è collegato in modo che forse un attaccante non si colleghi in codice malvagio.
Richard

Allora perché la libreria di runtime Go non è in un file DLL, in modo che possa essere condivisa tra tutti i file exe Go? Quindi un programma "ciao mondo" potrebbe essere di pochi KB, come previsto, invece di 2 MB. Avere l'intera libreria di runtime in ogni programma è un difetto fatale per l'alternativa altrimenti meravigliosa a MSVC su Windows.
David Spector

È meglio anticipare un'obiezione al mio commento: che Go è "staticamente collegato". Ok, allora niente DLL. Ma il collegamento statico non significa che devi collegare (legare) un'intera libreria, solo le funzioni che sono effettivamente utilizzate nella libreria!
David Spector

44

Considera il seguente programma:

package main

import "fmt"

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

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

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

Ottengo un file binario di circa 2 Mb.

La ragione di questo (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 ordiniamo al compilatore di 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 utilizzare la funzione incorporata 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 l'UPX-packing, quindi il sovraccarico del Go-runtime è di circa 700 Kb.


4
UPX comprime i binari e li decomprime al volo quando vengono eseguiti. Non lo ignorerei come un trucco senza spiegare cosa fa, poiché può essere utile in alcuni scenari. La dimensione binaria è leggermente ridotta a scapito del tempo di avvio e dell'utilizzo della RAM; inoltre, anche le prestazioni possono essere leggermente influenzate. Ad esempio, un eseguibile potrebbe essere ridotto al 30% della sua dimensione (ridotta) e impiegare 35 ms in più per l'esecuzione.
simlev

10

Si noti che il problema della dimensione binaria viene rilevato dal problema 6853 nel progetto golang / go .

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

perché non scriviamo quei nomi nella tabella dei simboli.

Considerando che il compilatore, l'assemblatore, il linker e il runtime per 1.5 saranno interamente in Go, puoi aspettarti un'ulteriore ottimizzazione.


Aggiornamento 2016 Go 1.7: questo è stato ottimizzato: vedere " Binari Go 1.7 più piccoli ".

Ma in questi giorni (aprile 2019), ciò che occupa più posto è runtime.pclntab.
Vedi " Perché i miei file eseguibili Go sono così grandi? Visualizzazione delle dimensioni degli eseguibili Go che utilizzano D3 " da Raphael 'kena' Poss .

Non è molto ben documentato, tuttavia questo commento dal codice sorgente di Go suggerisce il suo scopo:

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

Lo scopo di questa struttura dati è consentire al sistema runtime Go di produrre tracce di stack descrittive in caso di arresto anomalo o su richieste interne tramite l' runtime.GetStackAPI.

Quindi mi sembra utile. Ma perché è così grande?

L'URL https://golang.org/s/go12symtab nascosto nel file sorgente precedentemente collegato reindirizza a un documento che spiega cosa è successo tra Go 1.0 e 1.2. Parafrasando:

prima della 1.2, il linker Go emetteva una tabella di riga compressa e il programma la decomprimeva al momento dell'inizializzazione in fase di esecuzione.

in Go 1.2, è stata presa la decisione di pre-espandere la tabella di riga nel file eseguibile nel suo formato finale adatto per l'uso diretto in fase di esecuzione, senza un passaggio di decompressione aggiuntivo.

In altre parole, il team di Go ha deciso di ingrandire i file eseguibili per risparmiare tempo di inizializzazione.

Inoltre, osservando la struttura dei dati, sembra che la sua dimensione complessiva nei binari compilati sia super-lineare nel numero di funzioni nel programma, oltre alla dimensione di ciascuna funzione.

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


2
Non vedo cosa c'entra il suo linguaggio di implementazione. Devono utilizzare librerie condivise. Un po 'incredibile che non lo facciano già al giorno d'oggi.
user207421

3
@EJP: Perché hanno bisogno di utilizzare le librerie condivise?
Flimzy

10
@EJP, parte della semplicità di Go sta nel non utilizzare le librerie condivise. In effetti, Go non ha alcuna dipendenza, utilizza semplici chiamate di sistema. Basta distribuire un singolo binario e funziona. Se fosse diversamente, danneggerebbe in modo significativo la lingua e il suo ecosistema.
creker

11
Un aspetto spesso dimenticato dell'avere binari collegati staticamente è che rende possibile eseguirli in un Docker-container completamente vuoto. Dal punto di vista della sicurezza, questo è l'ideale. Quando il contenitore è vuoto, potresti essere in grado di entrare (se il binario collegato staticamente ha dei difetti), ma poiché non c'è niente da trovare nel contenitore, l'attacco si ferma qui.
Joppe
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.