Cumpri um programa hello world Go que gerou um executável nativo na minha máquina Linux. Mas fiquei surpreso ao ver o tamanho do simples programa Hello world Go, era de 1,9 MB!
Por que o executável de um programa tão simples em Go é tão grande?
Cumpri um programa hello world Go que gerou um executável nativo na minha máquina Linux. Mas fiquei surpreso ao ver o tamanho do simples programa Hello world Go, era de 1,9 MB!
Por que o executável de um programa tão simples em Go é tão grande?
dotnet publish -r win-x64 -p:publishsinglefile=true -p:publishreadytorun=true -p:publishtrimmed=true
gera um arquivo binário de aproximadamente 26 MB!
Respostas:
Esta pergunta exata aparece no FAQ oficial: Por que meu programa trivial é um binário tão grande?
Citando a resposta:
Os ligantes na cadeia ferramenta GC (
5l
,6l
, e8l
) fazer a ligação estática. Todos os binários Go, portanto, incluem o tempo de execução Go, junto com as informações de tipo de tempo de execução necessárias para dar suporte a verificações de tipo dinâmico, reflexão e até mesmo rastreamentos de pilha em tempo de pânico.Um programa simples em C "hello, world" compilado e vinculado estaticamente usando gcc no Linux tem cerca de 750 kB, incluindo uma implementação de
printf
. Um programa Go equivalente usandofmt.Printf
tem cerca de 1,9 MB, mas inclui suporte de tempo de execução mais poderoso e informações de tipo.
Portanto, o executável nativo do Hello World é de 1,9 MB porque contém um tempo de execução que fornece coleta de lixo, reflexão e muitos outros recursos (que seu programa pode não usar, mas está lá). E a implementação do fmt
pacote que você usou para imprimir o "Hello World"
texto (mais suas dependências).
Agora tente o seguinte: adicione outra fmt.Println("Hello World! Again")
linha ao seu programa e compile-o novamente. O resultado não será 2x 1,9 MB, mas apenas 1,9 MB! Sim, porque todas as bibliotecas usadas ( fmt
e suas dependências) e o runtime já estão adicionados ao executável (e assim apenas mais alguns bytes serão adicionados para imprimir o segundo texto que você acabou de adicionar).
Considere o seguinte programa:
package main
import "fmt"
func main() {
fmt.Println("Hello World!")
}
Se eu construir isso em minha máquina Linux AMD64 (Go 1.9), assim:
$ go build
$ ls -la helloworld
-rwxr-xr-x 1 janf group 2029206 Sep 11 16:58 helloworld
Recebo um binário com cerca de 2 Mb de tamanho.
A razão para isso (que foi explicada em outras respostas) é que estamos usando o pacote "fmt", que é bastante grande, mas o binário também não foi removido e isso significa que a tabela de símbolos ainda está lá. Se, em vez disso, instruirmos o compilador para remover o binário, ele se tornará muito menor:
$ go build -ldflags "-s -w"
$ ls -la helloworld
-rwxr-xr-x 1 janf group 1323616 Sep 11 17:01 helloworld
No entanto, se reescrevermos o programa para usar a função embutida print, em vez de fmt.Println, assim:
package main
func main() {
print("Hello World!\n")
}
E então compilar:
$ go build -ldflags "-s -w"
$ ls -la helloworld
-rwxr-xr-x 1 janf group 714176 Sep 11 17:06 helloworld
Acabamos com um binário ainda menor. Isso é tão pequeno quanto podemos obter sem recorrer a truques como empacotamento UPX, então a sobrecarga do tempo de execução Go é de aproximadamente 700 Kb.
Observe que o problema de tamanho binário é rastreado pelo problema 6853 no projeto golang / go .
Por exemplo, o commit a26c01a (para Go 1.4) reduz o hello world em 70kB :
porque não escrevemos esses nomes na tabela de símbolos.
Considerando que o compilador, o assembler, o vinculador e o tempo de execução do 1.5 estarão inteiramente em Go, você pode esperar mais otimização.
Atualização 2016 Go 1.7: isso foi otimizado: consulte " Binários menores do Go 1.7 ".
Mas hoje (abril de 2019), o que ocupa mais lugar é runtime.pclntab
.
Consulte " Por que meus arquivos executáveis Go são tão grandes? Visualização do tamanho dos executáveis Go usando D3 " de Raphael 'kena' Poss .
Não está muito bem documentado, no entanto, este comentário do código-fonte Go sugere seu propósito:
// A LineTable is a data structure mapping program counters to line numbers.
O objetivo desta estrutura de dados é permitir que o sistema de tempo de execução Go produza rastreamentos de pilha descritivos em uma falha ou em solicitações internas por meio do
runtime.GetStack
API.Portanto, parece útil. Mas por que é tão grande?
O URL https://golang.org/s/go12symtab oculto no arquivo de origem anteriormente vinculado redireciona para um documento que explica o que aconteceu entre Go 1.0 e 1.2. Parafrasear:
antes do 1.2, o vinculador Go emitia uma tabela de linha compactada e o programa a descompactava na inicialização em tempo de execução.
no Go 1.2, foi tomada a decisão de pré-expandir a tabela de linha no arquivo executável em seu formato final adequado para uso direto em tempo de execução, sem uma etapa de descompressão adicional.
Em outras palavras, a equipe de Go decidiu tornar os arquivos executáveis maiores para economizar tempo de inicialização.
Além disso, olhando para a estrutura de dados, parece que seu tamanho geral em binários compilados é superlinear no número de funções no programa, além do tamanho de cada função.