Jak zmniejszyć rozmiar skompilowanego pliku?

84

Porównajmy c i przejdźmy: Hello_world.c:

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

Hello_world.go:

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

Skompiluj oba:

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

i co to jest?

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

Około 1Mb Witaj, świecie. Czy ty żartujesz? Co robię źle?

(usuń Hello_go - tylko> 893K)

zyrg
źródło
2
Na komputerze Mac x86_64 plik binarny „Hello World” ma 1,3 MB, tak jak zakładam na komputerze z systemem Linux x64. Natomiast plik binarny ARM x32 jest tak samo duży jak plik binarny x86_32. Rozmiar w znacznym stopniu zależy od długości „słowa” odpowiedniej architektury. Na maszynach x32 jest 32-bitowy na x64 ma szerokość 64-bitową. Dlatego plik binarny x32 „Hello World” jest o około 30% mniejszy.
Alex
17
@ Nick: Biorąc pod uwagę, że GO jest sprzedawany jako język systemowy, myślę, że jest to uczciwe pytanie. Pracuję w systemach i nie zawsze mamy luksus 4 GB + RAM i ogromny dysk.
Ed S.,
3
Plik wykonywalny o rozmiarze 893 kb jest daleki od "4 GB + RAM i dużego dysku", a jak inni już zauważyli, obejmuje to statycznie połączone środowisko uruchomieniowe go, które można łatwo wykluczyć.
Nick Johnson,
22
Tak, jest to dalekie od prawdy i znam odpowiedź, ale jest to ważne pytanie, a postawa „kogo obchodzi zużycie pamięci” zwykle pochodzi z pracy w systemach, w których nie ma to znaczenia. Wydaje się, że myślisz, że ignorancja jest ok i lepiej po prostu nie zadawać pytań. I powiem jeszcze raz; czasami ~ 1 MB to dużo, oczywiście nie pracujesz w tym świecie. EDYCJA - Pracujesz w Google! lol. Nadal nie rozumiem jednak nastawienie „kogo to obchodzi”
Ed S.,
12
Najwyraźniej w dziale Java;)
Matt Joiner

Odpowiedzi:

29

Czy to problem, że plik jest większy? Nie wiem, czy Go, ale zakładam, że statycznie łączy pewną bibliotekę wykonawczą, co nie ma miejsca w przypadku programu C. Ale prawdopodobnie nie ma się czym martwić, gdy tylko twój program się powiększy.

Jak opisano tutaj , statyczne łączenie środowiska wykonawczego Go jest ustawieniem domyślnym. Ta strona zawiera również informacje o tym, jak skonfigurować dynamiczne łączenie.

Dirk Vollmar
źródło
2
Jest otwarty problem , z kamieniem milowym ustawionym dla Go1.5
Joe
79

Jeśli używasz systemu opartego na Uniksie (np. Linux lub Mac OSX), możesz spróbować usunąć informacje debugowania zawarte w pliku wykonywalnym, budując go z flagą -w:

go build -ldflags "-w" prog.go

Znacznie zmniejszają się rozmiary plików.

Aby uzyskać więcej informacji, odwiedź stronę GDB: http://golang.org/doc/gdb

Amged Rustom
źródło
3
To samo osiąga się za pomocą strippolecenia: go build prog.go; strip progwtedy otrzymaliśmy tak zwany plik wykonywalny w paski : plik wykonywalny ELF 64-bit LSB, x86-64, wersja 1 (SYSV), połączony dynamicznie (używa bibliotek współdzielonych), rozebrany
Alexander I.Grafov
1
Działa to w systemie Windows i zapewnia nieco mniejszy plik binarny w porównaniu z plikiem utworzonym przez normalne budowanie i usuwanie.
Isxek
9
@Axel: Usuwanie plików binarnych go wyraźnie nie jest obsługiwane i może w niektórych przypadkach powodować poważne błędy. Zobacz tutaj .
Flimzy
9
Obecnie w dokumentacji -wzamiast -s. Nie jestem pewien, jaka jest różnica, ale możesz zaktualizować swoją odpowiedź :-)
Dynom
2
@Dynom: -w wydaje się zmniejszać rozmiar, ale redukcja nie jest tak dobra, jak w przypadku -s. -s wydaje się sugerować -w: golang.org/cmd/link
arkod
42

Odpowiedź z 2016 roku:

1. Użyj Go 1.7

2. Skompiluj z go build -ldflags "-s -w"

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

3. Wtedy użycie upx, goupxnie jest już potrzebne od 1.6.

➜ ls -lh hello
-rwxr-xr-x 1 oneofone oneofone 367K May 26 20:49 hello*
OneOfOne
źródło
3
Chciałbym tutaj dodać, że prawdopodobnie obowiązują tutaj zwykłe zastrzeżenia dotyczące UPX. Zobacz to, aby uzyskać więcej informacji: stackoverflow.com/questions/353634/… (większość z nich dotyczy również systemów operacyjnych innych niż Windows).
Jonas
23

Pliki binarne Go są duże, ponieważ są połączone statycznie (z wyjątkiem powiązań bibliotek używających Cgo). Spróbuj łączyć statycznie program w C, a zobaczysz, że osiągnie on porównywalny rozmiar.

Jeśli jest to dla Ciebie naprawdę problem (w co trudno mi uwierzyć), możesz skompilować za pomocą gccgo i dynamicznie linkować.

Evan Shaw
źródło
19
Jest to również niezłe posunięcie jak na język, który nie jest jeszcze powszechnie przyjęty. Nikt nie jest zainteresowany zaśmiecaniem swoich systemów bibliotekami go system.
Matt Joiner
4
Twórcy Go wyraźnie preferują łączenie dynamiczne: szkodliwe.cat-v.org/software/dynamic-linking . Google statycznie łączy wszystkie swoje C i C ++: reddit.com/r/golang/comments/tqudb/…
Graham King
14
Myślę, że artykuł, do którego prowadzi link, stwierdza coś przeciwnego - twórcy Go wyraźnie preferują linkowanie statyczne.
Petar Donchev,
16

Powinieneś dostać goupx , to "naprawi" pliki wykonywalne Golang ELF do pracy upx. W niektórych przypadkach już zmniejszyłem rozmiar pliku o około 78% ~16MB >> ~3MB.

Współczynnik kompresji zwykle wynosi 25%, więc warto spróbować:

$ 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: -sflaga (strip) może jeszcze bardziej zmniejszyć plik bingoupx -s filename

marcio
źródło
1
To świetnie, dzięki! Od jakiegoś czasu ustawiam GOARCH = 386 i po prostu używam zwykłego upx.
codekoala
10
Nie jest to już potrzebne od wersji Go 1.6, możesz używać normalnego upx.
OneOfOne
11

utwórz plik o nazwie main.go, spróbujmy z prostym programem hello world.

package main

import "fmt"

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

Używam go w wersji 1.9.1

$ go version
 go version go1.9.1 linux/amd64

Skompiluj za pomocą standardowego go buildpolecenia.

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

Skompilujmy ponownie z, go buildale z ldflagsjak zasugerowano powyżej,

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

Rozmiar pliku zostaje zmniejszony o 30%.

Teraz użyjmy gccgo,

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

Budowanie iść z gccgo,

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

Rozmiar binarny został zmniejszony o prawie 100%. Chodźmy znów próbować budować nasz main.goz gccgoale z wbudowanym flagami,

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

Ostrzeżenie: Asgccgo pliki binarne zostały połączone dynamicznie. Jeśli masz plik binarny, który jest bardzo duży, twój plik binarny po kompilacji za pomocą gccgo nie zostanie zmniejszony o 100%, ale zostanie znacznie zmniejszony.

W porównaniu z gc, gccgo wolniej kompiluje kod, ale obsługuje bardziej zaawansowane optymalizacje, więc program związany z procesorem zbudowany przez gccgo będzie zwykle działał szybciej. Dostępne są wszystkie optymalizacje wdrożone w GCC na przestrzeni lat, w tym wstawianie, optymalizacje pętli, wektoryzacja, planowanie instrukcji i inne. Chociaż nie zawsze tworzy lepszy kod, w niektórych przypadkach programy skompilowane za pomocą gccgo mogą działać 30% szybciej.

Oczekuje się, że wydania GCC 7 będą zawierać pełną implementację bibliotek użytkownika Go 1.8. Podobnie jak w przypadku wcześniejszych wydań, środowisko wykonawcze Go 1.8 nie jest w pełni scalone, ale nie powinno to być widoczne dla programów Go.

Plusy:

  1. Zmniejszony rozmiar
  2. Zoptymalizowany.

Cons

  1. Powolny
  2. Nie można użyć najnowszej wersji go.

Możesz zobaczyć tutaj i tutaj .

nilsocket
źródło
Dziękuję Ci. Mam pytanie, jak czytam, gogccnie obsługuje procesora ARM, czy to prawda? Czy możesz zasugerować narzędzie, które zmniejszy rozmiar pliku kompilacji Golang?
M. Rostami
1
Jak kompilujesz z gccgo?
Witalij Zdanevich
10

Bardziej zwarty przykład hello-world:

package main

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

Ominęliśmy duży fmtpakiet i zauważalnie zmniejszono liczbę binarną:

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

Nie tak kompaktowy jak C, ale mierzony w K, a nie w M :) Ok, nie jest to ogólny sposób, po prostu pokazuje kilka sposobów optymalizacji rozmiaru: użyj paska i spróbuj użyć minimalnych pakietów. W każdym razie Go nie jest językiem do tworzenia małych plików binarnych.

Alexander I.Grafov
źródło
11
Ten kod korzysta z wbudowanej funkcji print () i nie jest to zalecane. I nie chodziło o to, jak zmniejszyć rozmiar kodu dostarczonego przykładowego programu, ale w bardziej ogólny sposób.
wldsvc
@wldsvc Zgadzam się uwaga. Chociaż nie rozumiesz, dlaczego print () jest złym sposobem wyświetlania danych wyjściowych? W przypadku prostych skryptów lub wyników debugowania jest to wystarczające.
Alexander I.Grafov
3
Zobacz doc.golang.org/ref/spec#Bootstrapping Te funkcje są udokumentowane pod kątem kompletności, ale nie gwarantuje się, że pozostaną w języku . Dla mnie oznacza to „nie używaj ich” w żadnym skrypcie z wyjątkiem jednorazowego debugowania.
wldsvc
@wldsvc print () nadal istnieje w 2020 roku, ale z tym samym zastrzeżeniem
Phani Rithvij
2

Odpowiedź 2018 na następną wersję Go 1.11, opublikowaną na Twitterze przez Brada Fitzpatricka (połowa czerwca 2018 r.):

Kilka minut temu sekcje DWARF w plikach binarnych #golang ELF są teraz skompresowane, więc na końcu pliki binarne są teraz mniejsze niż Go 1.10, nawet z wszystkimi dodatkowymi elementami debugowania na końcu.

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

Por. Wydanie Golang 11799 :

Kompresja naszych informacji debugowania może przynieść znaczne korzyści przy niewielkim rozmiarze pliku.

Zobacz więcej w commit 594eae5

cmd / link: kompresja sekcji DWARF w plikach binarnych ELF

Najtrudniejszą częścią tego jest to, że binarny kod układu (blk, elfshbits i różne inne rzeczy) zakłada stałe przesunięcie między lokalizacjami plików symboli i sekcji a ich wirtualnymi adresami.

Kompresja oczywiście przerywa to stałe przesunięcie.
Ale musimy przypisać wirtualne adresy do wszystkiego przed kompresją, aby rozwiązać relokacje przed kompresją.

W rezultacie kompresja musi ponownie obliczyć „adres” sekcji DWARF i symboli na podstawie ich skompresowanego rozmiaru.
Na szczęście znajdują się one na końcu pliku, więc nie zakłóca to żadnych innych sekcji ani symboli. (I oczywiście jest zaskakująca ilość kodu, który zakłada, że ​​segment DWARF jest ostatni, więc jakie jest jeszcze jedno miejsce?)

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 wspomina :

To pomaga tylko na maszynach używających ELF.
Pliki binarne są nadal zbyt duże i rosną.

Brad odpowiedział:

Przynajmniej to coś. Miało być znacznie gorzej.
Zatrzymałem krwawienie na jedno uwolnienie.

Powody : informacje o debugowaniu, ale także rejestracja mapy dla GC, aby każda instrukcja była punktem zapisu.

VonC
źródło
2

Domyślnie gcc łączy się dynamicznie i idzie - statycznie.

Ale jeśli połączysz swój kod C statycznie, możesz otrzymać plik binarny o większym rozmiarze.

W moim przypadku:

  • go x64 (1.10.3) - wygenerowany plik binarny o rozmiarze 1214208 bajtów
  • gcc x64 (6.2.0) - wygenerowany plik binarny o rozmiarze 1421312 bajtów

oba pliki binarne są połączone statycznie i bez debug_info.

go build -ldflags="-s -w" -o test-go test.go
gcc -static -s -o test-c test.c
Witalij
źródło
1

Plik binarny zawiera domyślnie moduł odśmiecania pamięci, system planowania, który zarządza procedurami go, oraz wszystkie importowane biblioteki.

Rezultatem jest minimalny rozmiar około 1 Mb.

JulienFr
źródło
1

Od wersji Go 1.8 możesz także użyć nowego systemu wtyczek, aby podzielić plik binarny na coś, co przypomina biblioteki współdzielone. W tej wersji działa tylko w systemie Linux, ale inne platformy będą prawdopodobnie obsługiwane w przyszłości.

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

Joppe
źródło