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

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
На Mac x86_64 двоичный файл «Hello World» имеет размер 1,3 МБ, как и на машине x64 Linux, я полагаю. Напротив, двоичный файл ARM x32 такой же большой, как двоичный файл x86_32. Размер существенно зависит от длины «слова» соответствующей архитектуры. На машинах x32 это 32 бит, на x64 - 64 бит. Следовательно, двоичный файл x32 «Hello World» примерно на 30% меньше.
Alex
17
@Nick: Учитывая, что GO позиционируется как системный язык, я думаю, что это справедливый вопрос. Я работаю в системах, и мы не всегда можем позволить себе роскошь 4 ГБ + ОЗУ и огромный диск.
Эд С.
3
Исполняемый файл размером 893 КБ далек от «4 ГБ + ОЗУ и огромного диска», и, как уже отмечали другие, он включает статически связанную среду выполнения go, которую можно легко исключить.
Ник Джонсон,
22
Да, это очень далеко, и я знаю ответ, но это верный вопрос, и отношение «кого волнует потребление памяти», как правило, исходит из работы в системах, где это не имеет значения. Вы, кажется, думаете, что невежество это нормально, и лучше просто не задавать вопросы. И я повторюсь; иногда ~ 1 МБ - это много, вы, очевидно, не работаете в этом мире. EDIT - вы работаете в Google! lol. Я все еще не понимаю «Кого это волнует»,
Эд С.
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

Amged Rustom
источник
3
То же самое достигается с помощью stripкоманды: go build prog.go; strip progтогда мы получили так называемый чередующийся исполняемый файл: ELF 64-битный LSB исполняемый файл, x86-64, версия 1 (SYSV), динамически скомпонованный (использует общие библиотеки), разделенный
Александр Графов
1
Это работает в Windows и обеспечивает немного меньший размер двоичного файла по сравнению с файлом, созданным при обычной сборке и удалении.
Isxek
9
@Axel: Удаление двоичных файлов go явно не поддерживается и в некоторых случаях может вызвать серьезные ошибки. Смотрите здесь .
Flimzy
9
В настоящее время в документации -wвместо -s. Не уверен, в чем разница, но, возможно, вы захотите обновить свой ответ :-)
Dynom
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*
OneOfOne
источник
3
Я хотел бы добавить здесь, что здесь, вероятно, применимы обычные предостережения относительно UPX. См. Это для получения дополнительной информации: stackoverflow.com/questions/353634/… (большая часть этого относится и к операционным системам, отличным от Windows).
Jonas
23

Бинарные файлы Go имеют большой размер, потому что они статически связаны (за исключением связывания библиотек с помощью cgo). Попробуйте статически связать программу C, и вы увидите, что она вырастет до сопоставимого размера.

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

Эван Шоу
источник
19
Также это своего рода хороший ход для языка, который еще не принят повсеместно. Никто не заинтересован в том, чтобы загромождать свои системы системными библиотеками go.
Мэтт Джойнер
4
Создатели Go явно предпочитают динамическое связывание: dangerous.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флаг (полоса) может еще больше уменьшить размер файла bingoupx -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.

Вы можете увидеть здесь и здесь .

nilsocket
источник
Спасибо. У меня вопрос, как я читал, gogccне поддерживает процессор ARM, верно? И, можете ли вы предложить инструмент, который уменьшает размер файла сборки Golang?
М. Ростами
1
Как вы компилируете с помощью gccgo?
Виталий Зданевич
10

Более компактный пример hello-world:

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 01
@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

В нем пошагово показано, как уменьшить статический двоичный образец hello-world размером 2 МБ до статического двоичного значения 15 КБ или динамического двоичного кода 10 КБ. Конечно, есть много ограничений.

Дмитрий Окунев
источник
2

2018 ответ на следующий Go 1.11, в твиттере по Брэд Фитцпатрик ( в середине июня 2018 года):

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

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

Ср. Проблема с Golang 11799 :

Сжатие нашей отладочной информации может дать значительный выигрыш в меньшем размере файла.

См. Больше в коммите 594eae5

cmd / link: сжатие разделов DWARF в двоичных файлах ELF

Самая сложная часть этого заключается в том, что код двоичного макета (blk, elfshbits и другие вещи) предполагает постоянное смещение между местоположениями файлов символов и разделов и их виртуальными адресами.

Сжатие, конечно, нарушает это постоянное смещение.
Но нам нужно назначить виртуальные адреса всему перед сжатием, чтобы разрешить перемещения перед сжатием.

В результате при сжатии необходимо повторно вычислять «адрес» разделов и символов DWARF на основе их сжатого размера.
К счастью, они находятся в конце файла, так что это не мешает другим разделам или символам. (И, конечно, есть удивительное количество кода, который предполагает, что сегмент 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, чтобы любая инструкция могла быть точкой сохранения.

VonC
источник
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 Мб.

JulienFr
источник
1

Начиная с Go 1.8 вы также можете использовать новую систему плагинов, чтобы разделить двоичный файл на что-то, напоминающее общие библиотеки. В этом выпуске он работает только в Linux, но, вероятно, в будущем будут поддерживаться и другие платформы.

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

Joppe
источник