Як зменшити розмір скомпільованого файлу?


84

Давайте порівняємо c і підемо: Hello_world.c:

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

Hello_world.go:

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

Складіть обидва:

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

і ... що це?

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

Близько 1 Мб Привіт світ. Ви жартуєте? Що я роблю неправильно?

(смужка Hello_go -> 893K лише)


2
На x86_64 Mac двійковий файл "Hello World" становить 1,3 МБ, як на машині x64 Linux, я вважаю. На відміну від цього, двійковий файл ARM x32 настільки ж великий, як двійковий файл x86_32. Розмір суттєво залежить від довжини "слова" відповідної архітектури. На машинах x32 це 32 біти, на x64 ширина 64 біти. Тому двійковий файл x32 "Hello World" приблизно на 30% менший.
Alex

17
@ Нік: Враховуючи, що GO продається як системна мова, я думаю, це справедливе питання. Я працюю в системах, і у нас не завжди є розкіш 4 ГБ оперативної пам’яті та величезний диск.
Ed S.

3
Виконуваний файл 893 кб - це далеко не "4 ГБ оперативної пам'яті і величезний диск", і, як вже вказували інші, сюди входить статично пов'язаний час роботи, який можна легко виключити.
Нік Джонсон,

22
Так, це далеко не все, і я знаю відповідь, але це слушне питання, і "хто дбає про споживання пам'яті", як правило, походить від роботи в системах, де це не має значення. Ви, здається, думаєте, що незнання це нормально, і краще просто не задавати питань. І я ще раз скажу; іноді ~ 1 МБ - це багато, ви, очевидно, не працюєте в цьому світі. РЕДАКТУВАТИ - Ви працюєте в Google! ха-ха. Я все ще не отримую Хоча ставлення до кого?
Ед С.

12
Очевидно, у відділі Java;)
Метт Джойнер

Відповіді:


29

Чи проблема в тому, що файл більший? Я не знаю Go, але я б припустив, що він статично пов'язує деяку бібліотеку виконання, що не стосується програми C. Але, мабуть, це не про що турбуватися, як тільки ваша програма стає більшою.

Як описано тут , за замовчуванням використовується статичне зв’язування середовища виконання Go. Ця сторінка також розповідає, як налаштувати динамічне зв’язування.


2
Є відкрите видання , основний етап якого встановлений для Go1.5
Джо

79

Якщо ви використовуєте систему на основі Unix (наприклад, Linux або Mac OSX), ви можете спробувати видалити інформацію про налагодження, що міститься у виконуваному файлі, побудувавши її з позначкою -w:

go build -ldflags "-w" prog.go

Розміри файлів різко зменшуються.

Щоб отримати докладнішу інформацію, відвідайте сторінку GDB: http://golang.org/doc/gdb


3
Те саме досягається за допомогою stripкоманди: go build prog.go; strip progтоді ми отримали так званий смугастий виконуваний файл: ELF 64-розрядний LSB-виконаний файл, x86-64, версія 1 (SYSV), динамічно пов’язаний (використовує спільні бібліотеки), позбавлений
Олександр Іванович Графов

1
Це працює в Windows і надає дещо менший двійковий файл у порівнянні з тим, який створюється звичайною побудовою та видаленням.
Isxek,

9
@Axel: Видалення двійкових файлів go категорично не підтримується, і в деяких випадках може спричинити серйозні помилки. Дивіться тут .
Неміцний

9
В даний час документація перелічує -wзамість -s. Не впевнений, у чому різниця, але, можливо, ви захочете оновити свою відповідь :-)
Dynom 21.03.16

2
@Dynom: -w, здається, дещо зменшує розмір, але зменшення не таке гарне, як при -s. -s, здається, означає -w: golang.org/cmd/link
arkod

42

Відповідь 2016 року:

1. Використовуйте Go 1.7

2. Скласти с go build -ldflags "-s -w"

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

3. Потім використовувати upx, goupxбільше не потрібно з 1.6.

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

3
Тут я хотів би додати, що тут, ймовірно, застосовуються звичайні застереження щодо UPX. Див. Це для отримання додаткової інформації: stackoverflow.com/questions/353634/… (більша частина цього стосується і операційних систем, що не входять до складу Windows).
Йонас,

23

Бінарні файли Go є великими, оскільки вони статично пов'язані (за винятком прив'язок бібліотек за допомогою cgo). Спробуйте статично зв’язати програму С, і ви побачите, що вона зросте до порівнянних розмірів.

Якщо це дійсно проблема для вас (у що мені важко повірити), ви можете скомпілювати за допомогою gccgo і динамічно посилатись.


19
Також це свого роду приємний крок до мови, яка ще не є загальноприйнятою. Ніхто не зацікавлений в захаращенні своїх систем бібліотеками go.
Matt Joiner

4
Творці Go явно віддають перевагу динамічному зв'язуванню: škod.cat-v.org/software/dynamic-linking . Google статично пов'язує всі свої C та C ++: reddit.com/r/golang/comments/tqudb/…
Грем Кінг

14
Я думаю, що пов’язана стаття стверджує протилежне - творці Go явно віддають перевагу статичним посиланням.
Петро Дончев

16

Ви повинні отримати goupx , він "виправить" виконавчі файли Golang ELF для роботи upx. У мене в деяких випадках вже зменшується розмір файлу приблизно на 78% ~16MB >> ~3MB.

Ступінь стиснення зазвичай становить 25%, тому варто спробувати:

$ 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.

ДОПОЛНИТЕЛЬНО: -sпрапор (смужка) може ще більше зменшити файл смітникаgoupx -s filename


1
Це чудово, дякую! Я вже деякий час встановлюю GOARCH = 386 і просто використовую звичайний upx.
codekoala

10
З Go 1.6 це більше не потрібно, ви можете використовувати звичайний upx.
OneOfOne

11

створити файл з іменем main.go, спробуємо за допомогою простої програми hello world.

package main

import "fmt"

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

Я використовую go версію 1.9.1

$ go version
 go version go1.9.1 linux/amd64

Скомпілюйте зі стандартною go buildкомандою.

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

Давайте ще раз скомпілюємо з, go buildале ldflagsяк було запропоновано вище,

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

Розмір файлу зменшено на 30%.

Тепер давайте використовувати gccgo,

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

Будівництво йти з gccgo,

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

Бінарний розмір зменшується майже на 100%. Давайте ще раз спробуємо побудувати наш за main.goдопомогою, gccgoале з прапорами побудови,

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

Попередження: Якgccgo двійкові файли динамічно пов’язані. Якщо у вас є двійковий файл дуже великого розміру, ваш двійковий файл при компіляції з gccgo не зменшиться на 100%, але він зменшиться в розмірі на значну суму.

Порівняно з gc, gccgo повільніше компілює код, але підтримує потужніші оптимізації, тому програма, пов'язана з процесором, побудована gccgo, зазвичай працює швидше. Доступні всі оптимізації, впроваджені в GCC протягом багатьох років, включаючи вбудовування, оптимізацію циклу, векторизацію, планування інструкцій тощо. Хоча це не завжди дає кращий код, в деяких випадках програми, скомпільовані за допомогою gccgo, можуть працювати на 30% швидше.

Очікується, що випуски GCC 7 включатимуть повну реалізацію бібліотек користувачів Go 1.8. Як і у попередніх версіях, середовище виконання Go 1.8 не повністю об’єднано, але це не повинно бути видимим для програм Go.

Плюси:

  1. Зменшений розмір
  2. Оптимізовано.

Мінуси

  1. Повільно
  2. Не вдається використовувати останню версію go.

Ви можете побачити тут і тут .


Дякую. У мене є питання, оскільки я читав, gogccне підтримує ARM-процесор, чи не так? І, чи можете ви запропонувати інструмент, який зменшує розмір файлу збірки Golang?
М. Ростамі,

1
Як ви компілюєте за допомогою gccgo?
Віталій Зданевич

10

Приклад більш компактного привіт-світу:

package main

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

Ми пропустили великий fmtпакет і помітно зменшили двійковий файл:

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

Не настільки компактний, як C, але, хоча вимірюється в K, а не M :) Добре, це не загальний спосіб, який просто показує деякі способи оптимізації розміру: використовуйте смужку та намагайтеся використовувати мінімальні пакети. У будь-якому випадку Go - це не мова для створення дрібних двійкових файлів.


11
Цей код використовує вбудовану функцію print (), і така практика не рекомендується. І питання полягав не в тому, як зменшити розмір коду наданої вибірки програми, а в більш загальному вигляді.
wldsvc

@wldsvc Я згоден з Вашою заміткою. Хоча і не розумію, чому print () - поганий спосіб виводу на дисплей? Для простих скриптів або для виведення налагодження це досить добре.
Олександр Іванович Графов,

3
Див. Doc.golang.org/ref/spec#Bootstrapping Ці функції задокументовані для повноти, але вони не гарантують збереження мови . Для мене це означає "не використовувати їх" у будь-якому сценарії, окрім одноразової налагодження.
wldsvc

@wldsvc print () все ще існує в 2020 році, але з тим самим застереженням
Phani Rithvij

3

Ви можете поглянути на моє невелике дослідження з цього питання: https://github.com/xaionaro/documentation/blob/master/golang/reduce-binary-size.md

Він покроково показує, як зменшити статично-двійковий зразок привітного світу 2 МБ до статичного двійкового 15 КБ або до динамічно-двійкового 10 КБ. Звичайно, існує багато обмежень.


2

Відповідь 2018 року для наступного Go 1.11, як написано в Твіттері по Бред Фітцпатрік ( в середині червня 2018 роки):

Станом на кілька хвилин тому, розділи DWARF у файлах подвійних файлів #golang ELF тепер стискаються, тож на кінчиках двійкові файли тепер менші за Go 1.10, навіть з усіма додатковими налагоджувальними матеріалами на кінчику.

https://pbs.twimg.com/media/DfwkBaqUEAAb8-Q.jpg:великий

Пор. Випуск Golang 11799 :

Стискання нашої інформації про налагодження може забезпечити значний вигідний розмір файлу.

Дивіться більше в коміті 594eae5

cmd / link: стиснути розділи DWARF у двійкові файли ELF

Найскладніша частина цього полягає в тому, що двійковий код макета (blk, elfshbits та різні інші речі) передбачає постійний зсув між розташуванням файлів символів та розділів та їх віртуальними адресами.

Стиснення, звичайно, порушує це постійне зміщення.
Але нам потрібно призначити віртуальні адреси усім перед стисненням, щоб вирішити переміщення перед стисненням.

Як результат, для стиснення потрібно повторно обчислити «адресу» розділів і символів ГНОК на основі їх стиснутого розміру.
На щастя, вони знаходяться в кінці файлу, тому це не порушує жодних інших розділів чи символів. (І, звичайно, є дивовижна кількість коду, який передбачає, що сегмент DWARF виходить останнім, то що ще на одне місце?)

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%

Роб Пайк згадує :

Це допомагає лише на машинах, які використовують ELF.
Бінарні файли все ще занадто великі і зростають.

Бред відповів:

Принаймні це щось. Скоро мало бути набагато гірше.
Зупинив кровотечу на один випуск.

Причини : Налагодження інформації, а також реєстрація карти для GC, щоб будь-яка інструкція була точкою збереження.


2

За замовчуванням gcc посилається динамічно, а статично.

Але якщо ви зв'яжете свій код C статично, ви можете отримати двійковий файл із більшим розміром.

У моєму випадку:

  • go x64 (1.10.3) - сформований двійковий файл розміром 1214208 байт
  • gcc x64 (6.2.0) - сформований двійковий файл розміром 1421312 байт

обидва двійкові файли мають статичну зв'язок і без debug_info.

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

1

Бінарний файл за замовчуванням містить збирач сміття, систему планування, яка керує підпрограмами go, та всі бібліотеки, які ви імпортуєте.

Результат - мінімальний розмір близько 1 Мб.


1

У Go 1.8 ви також можете використовувати нову систему плагінів, щоб розділити двійковий файл на щось подібне до спільних бібліотек. У цьому випуску він працює лише на Linux, але інші платформи, ймовірно, будуть підтримуватися в майбутньому.

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.