¿Cómo reducir el tamaño del archivo compilado?

82

Comparemos cy vayamos: Hello_world.c:

#include<stdio.h>
int main(){
    printf("Hello world!");
}

Hello_world.go:

package main
import "fmt"
func main(){
    fmt.Printf("Hello world!")
}

Compila ambos:

$gcc Hello_world.c -o Hello_c 
$8g Hello_world.go -o Hello_go.8
$8l Hello_go.8 -o Hello_go

¿Y qué es eso?

$ls -ls
... 5,4K 2010-10-05 11:09 Hello_c
... 991K 2010-10-05 11:17 Hello_go

Aproximadamente 1Mb Hola mundo. ¿Me estás tomando el pelo? ¿Qué hice mal?

(tira Hello_go -> 893K solamente)

zyrg
fuente
2
En una Mac x86_64, el binario "Hello World" es de 1,3 MB, como en una máquina Linux x64, supongo. En contraste, el binario ARM x32 es tan grande como el binario x86_32. El tamaño depende significativamente de la longitud de la "palabra" de la arquitectura respectiva. En máquinas x32 es de 32 bits en x64 tiene 64 bits de ancho. Por lo tanto, el binario x32 "Hello World" es aproximadamente un 30% más pequeño.
Alex
17
@Nick: Teniendo en cuenta que GO se comercializa como un lenguaje de sistemas, creo que es una pregunta justa. Trabajo en sistemas y no siempre tenemos el lujo de 4GB + de RAM y un disco enorme.
Ed S.
3
Un ejecutable de 893kb está muy lejos de "4GB + de RAM y un disco enorme", y como otros ya han señalado, esto incluye el tiempo de ejecución de go vinculado estáticamente, que puede excluirse fácilmente.
Nick Johnson
22
Sí, está muy lejos, y sé la respuesta, pero es una pregunta válida y la actitud de "a quién le importa el consumo de memoria" tiende a provenir de trabajar en sistemas donde no importa. Parece pensar que la ignorancia está bien y es mejor no hacer preguntas. Y lo diré de nuevo; a veces ~ 1 MB es mucho, obviamente no trabajas en ese mundo. EDITAR - ¡Trabajas en Google! jaja. Todavía no entiendo Sin embargo, la actitud de 'a quién le importa'
Ed S.
12
Evidentemente en el departamento de Java;)
Matt Joiner

Respuestas:

29

¿Es un problema que el archivo sea más grande? No sé Go, pero supongo que enlaza estáticamente alguna biblioteca en tiempo de ejecución, lo que no es el caso del programa C. Pero probablemente eso no sea nada de qué preocuparse tan pronto como su programa crezca.

Como se describe aquí , la vinculación estática del tiempo de ejecución de Go es la opción predeterminada. Esa página también le indica cómo configurar los enlaces dinámicos.

Dirk Vollmar
fuente
2
Hay un problema abierto , con el hito establecido para Go1.5
Joe
80

Si está utilizando un sistema basado en Unix (por ejemplo, Linux o Mac OSX), puede intentar eliminar la información de depuración incluida en el ejecutable compilándolo con el indicador -w:

go build -ldflags "-w" prog.go

Los tamaños de archivo se reducen drásticamente.

Para obtener más detalles, visite la página del BGF: http://golang.org/doc/gdb

Amged Rustom
fuente
3
Lo mismo se logra con el stripcomando: go build prog.go; strip progluego obtuvimos el llamado ejecutable rayado : ejecutable LSB ELF de 64 bits, x86-64, versión 1 (SYSV), vinculado dinámicamente (usa bibliotecas compartidas), eliminado
Alexander I.Grafov
1
Esto funciona en Windows y proporciona un archivo binario un poco más pequeño en comparación con el que se produce al compilar normalmente y eliminar.
Isxek
9
@Axel: La eliminación de binarios go no está expresamente admitida y puede causar errores graves en algunos casos. Vea aquí .
Flimzy
9
Actualmente, la documentación enumera -w, en lugar de -s. No estoy seguro de cuál es la diferencia, pero es posible que desee actualizar su respuesta :-)
Dynom
1
@Dynom: -w parece reducir un poco el tamaño, pero la reducción no es tan buena como con -s. -s parece implicar -w: golang.org/cmd/link
arkod
42

La respuesta de 2016:

1. Utilice Go 1.7

2. Compilar con go build -ldflags "-s -w"

➜ ls -lh hello
-rwxr-xr-x 1 oneofone oneofone 976K May 26 20:49 hello*

3. Luego use upx, goupxya no es necesario desde 1.6.

➜ ls -lh hello
-rwxr-xr-x 1 oneofone oneofone 367K May 26 20:49 hello*
Uno de uno
fuente
3
Me gustaría agregar aquí que las advertencias habituales sobre UPX probablemente se apliquen aquí. Consulte esto para obtener más información: stackoverflow.com/questions/353634/… (gran parte de esto también se aplica a sistemas operativos que no son Windows).
Jonas
23

Los binarios de Go son grandes porque están vinculados estáticamente (a excepción de los enlaces de biblioteca que utilizan cgo). Intente vincular estáticamente un programa en C y verá que crece a un tamaño comparable.

Si esto es realmente un problema para usted (que me cuesta creer), puede compilar con gccgo y vincular dinámicamente.

Evan Shaw
fuente
19
También es un buen movimiento para un idioma que aún no se ha adoptado universalmente. A nadie le interesa saturar sus sistemas con las librerías del sistema go.
Matt Joiner
4
Los creadores de Go prefieren explícitamente los enlaces dinámicos: dañino.cat - v.org / software/ dynamic-linking . Google enlaza estáticamente todos sus C y C ++: reddit.com/r/golang/comments/tqudb/…
Graham King
13
Creo que el artículo vinculado dice lo contrario: los creadores de Go prefieren explícitamente los enlaces estáticos.
Petar Donchev
17

Debería obtener goupx , "arreglará" los ejecutables de Golang ELF para trabajar upx. Ya tengo una reducción del tamaño de archivo de alrededor del 78% en algunos casos ~16MB >> ~3MB.

La relación de compresión suele ser del 25%, por lo que vale la pena intentarlo:

$ go get github.com/pwaller/goupx
$ go build -o filename
$ goupx filename

>>

2014/12/25 10:10:54 File fixed!

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
  16271132 ->   3647116   22.41%  linux/ElfAMD   filename                            

Packed 1 file.

EXTRA: la -sbandera (tira) puede encoger aún más el archivo bingoupx -s filename

marcio
fuente
1
¡Esto es excelente, gracias! He estado configurando GOARCH = 386 y solo estoy usando upx normal por un tiempo.
codekoala
9
Esto ya no es necesario a partir de Go 1.6, puede usar el upx normal.
OneOfOne
12

cree un archivo llamado main.go, intentemos con un programa simple de hola mundo.

package main

import "fmt"

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

Yo uso go versión 1.9.1

$ go version
 go version go1.9.1 linux/amd64

Compile con go buildcomando estándar .

$ go build main.go
$ ls -lh
-rwxr-xr-x-x 1 nil nil 1.8M Oct 27 07:47 main

Compilemos una vez más con go buildpero con ldflagscomo se sugirió anteriormente,

$ go build -ldflags "-s -w" main.go
$ ls -lh
-rwxr-xr-x-x 1 nil nil 1.2M Oct 27 08:15 main

El tamaño del archivo se reduce en un 30%.

Ahora, usemos gccgo,

$ go version
 go version go1.8.1 gccgo (GCC) 7.2.0 linux/amd64

La construcción de ir con gccgo,

$ go build main.go
$ ls -lh
-rwxr-xr-x 1 nil nil 34K Oct 27 12:18 main

El tamaño binario se reduce casi en un 100%. Intentemos una vez más construir nuestro main.gocon gccgopero con banderas de compilación,

$ go build -gccgoflags "-s -w" main.go
-rwxr-xr-x 1 nil nil 23K Oct 27 13:02 main

Advertencia: ya que los gccgobinarios se vincularon dinámicamente. Si tiene un binario que es muy grande en tamaño, su binario cuando se compila con gccgo no se reducirá en un 100%, pero se reducirá en tamaño en una cantidad considerable.

En comparación con gc, gccgo es más lento para compilar código pero admite optimizaciones más potentes, por lo que un programa vinculado a la CPU creado por gccgo generalmente se ejecutará más rápido. Todas las optimizaciones implementadas en GCC a lo largo de los años están disponibles, incluida la inserción, las optimizaciones de bucle, la vectorización, la programación de instrucciones y más. Si bien no siempre produce un mejor código, en algunos casos los programas compilados con gccgo pueden ejecutarse un 30% más rápido.

Se espera que las versiones de GCC 7 incluyan una implementación completa de las bibliotecas de usuario de Go 1.8. Al igual que con versiones anteriores, el tiempo de ejecución de Go 1.8 no está completamente combinado, pero eso no debería ser visible para los programas de Go.

Pros:

  1. Tamaño reducido
  2. Optimizado.

Contras

  1. Lento
  2. No se puede utilizar la última versión de go.

Puedes ver aquí y aquí .

nilsocket
fuente
Gracias. Tengo una pregunta, mientras leo, gogccno es compatible con el procesador ARM, ¿verdad? Y, ¿puede sugerir una herramienta que reduzca el tamaño del archivo de compilación de Golang?
M. Rostami
¿Cómo se compila con gccgo?
Vitaly Zdanevich
10

Ejemplo de hello-world más compacto:

package main

func main() {
  print("Hello world!")
}

Somos grandes fmtpaquetes esquiados y binarios notablemente reducidos:

  $ go build hello.go
  $ ls -lh hello
  ... 259K ... hello2
  $ strip hello
  $ ls -lh hello
  ... 162K ... hello2

No tan compacto como C, pero aunque medido en K, no en M :) Ok, no es una forma general, solo muestra algunas formas de optimizar un tamaño: use strip e intente usar paquetes mínimos. De todos modos, Go no es un lenguaje para hacer binarios de tamaño pequeño.

Alejandro I.Grafov
fuente
11
Este código utiliza la función integrada print () y no se recomienda esta práctica. Y la pregunta no era cómo reducir el tamaño del código del programa de muestra proporcionado, sino de una manera más general.
wldsvc
@wldsvc Estoy de acuerdo con su nota. ¿Aunque no entiendes por qué print () es una mala forma de mostrar la salida? Para scripts simples o para resultados de depuración, es suficientemente bueno.
Alexander I.Grafov
3
Consulte doc.golang.org/ref/spec#Bootstrapping Estas funciones están documentadas para garantizar su integridad, pero no se garantiza que permanezcan en el idioma . Para mí, significa "no los use" en ningún script, excepto para fines de depuración única.
wldsvc
3

2018 respuesta para la próxima Go 1.11, como tuiteó por Brad Fitzpatrick (mediados de junio de 2018):

Hace unos minutos, las secciones DWARF en los binarios #golang ELF ahora están comprimidas, por lo que en tip los binarios ahora son más pequeños que Go 1.10, incluso con todas las cosas de depuración adicionales en tip.

https://pbs.twimg.com/media/DfwkBaqUEAAb8-Q.jpg:large

Cf. Problema de Golang 11799 :

Comprimir nuestra información de depuración puede ofrecer una ganancia significativa y barata de tamaño de archivo.

Ver más en el compromiso 594eae5

cmd / link: comprime secciones DWARF en binarios ELF

La parte más complicada de esto es que el código de diseño binario (blk, elfshbits y varias otras cosas) asume un desplazamiento constante entre las ubicaciones de los archivos de símbolos y secciones y sus direcciones virtuales.

La compresión, por supuesto, rompe este desplazamiento constante.
Pero necesitamos asignar direcciones virtuales a todo antes de la compresión para resolver las reubicaciones antes de la compresión.

Como resultado, la compresión necesita volver a calcular la "dirección" de las secciones y símbolos DWARF en función de su tamaño comprimido.
Afortunadamente, estos están al final del archivo, por lo que esto no perturba ninguna otra sección o símbolo. (Y hay, por supuesto, una sorprendente cantidad de código que asume que el segmento DWARF es el último, así que, ¿cuál es un lugar más?)

name        old exe-bytes   new exe-bytes   delta
HelloSize      1.60MB ± 0%     1.05MB ± 0%  -34.39%  (p=0.000 n=30+30)
CmdGoSize      16.5MB ± 0%     11.3MB ± 0%  -31.76%  (p=0.000 n=30+30)
[Geo mean]     5.14MB          3.44MB       -33.08%

Rob Pike menciona :

Eso solo ayuda en máquinas que usan ELF.
Los binarios todavía son demasiado grandes y están creciendo.

Brad respondió:

Al menos es algo. Estaba a punto de ser mucho peor.
Detuvo el sangrado para una liberación.

Motivos : depure la información, pero también registre el mapa para que el GC permita que cualquier instrucción sea un punto de guardado.

VonC
fuente
1

El binario contiene de forma predeterminada el recolector de basura, el sistema de programación que administra las rutinas de marcha y todas las bibliotecas que importa.

El resultado es un tamaño mínimo de aproximadamente 1 Mb.

JulienFr
fuente
1

Desde Go 1.8 también puede usar el nuevo sistema de complementos para dividir su binario en algo que se parezca a bibliotecas compartidas. Para esta versión, solo funciona en Linux, pero probablemente se admitirán otras plataformas en el futuro.

https://tip.golang.org/pkg/plugin/

Joppe
fuente
1

Por defecto, gcc enlaza dinámicamente y va - estáticamente.

Pero si enlaza su código C de forma estática, puede obtener un binario con un tamaño mayor.

En mi caso:

  • go x64 (1.10.3) - binario generado con un tamaño de 1214208 bytes
  • gcc x64 (6.2.0) - binario generado con tamaño 1421312 byte

ambos binarios están vinculados estáticamente y sin debug_info.

go build -ldflags="-s -w" -o test-go test.go
gcc -static -s -o test-c test.c
Vitaly
fuente