Комментарии 52
Сложно представить, конечно, сотню каких-то утилиток, но даже если и так, то в наше-то время дисковое пространство уже давно не проблема.
Кроме того, если размер очень критичен, то почему не скомпилировать через gccgo?
Ещё с большим размером может быть проблема с версионным контролем. Но опять же, в нём совершенно негоже хранить скомпилированные бинарники.
Кроме того, если размер очень критичен, то почему не скомпилировать через gccgo?
Ещё с большим размером может быть проблема с версионным контролем. Но опять же, в нём совершенно негоже хранить скомпилированные бинарники.
Бинарники при исполнении мапятся в оперативную память, причем разные инстансы одного бинарника в норме все ссылаются на одну и ту же память. Чем бинарник меньше — тем меньше потребление оперативки и меньше сопутствующие накладные расходы, за счет меньшего числа cache miss работать все будет немного быстрее.
Но в целом экономия на спичках конечно.
Но в целом экономия на спичках конечно.
А нельзя чтоли рантайм вынести в динамически подключаемую библиотеку?
Добро пожаловать в мир golang'а.
И после этого ещё спрашивают, почему go не заменит Си.
И после этого ещё спрашивают, почему go не заменит Си.
А он должет заменить Си?
Были поползновения. Быстро закончилось, но как витающий стереотип — местами замечаю.
Ну как «убийцы» себя позиционируют, по моему, только Rust и Dlang. И то — «Убийцы C++».
А Go если и будет убийцей, то, скорее, всяких PHP/Python/Perl и иже с ними.
А Go если и будет убийцей, то, скорее, всяких PHP/Python/Perl и иже с ними.
Ну, чисто формально, компилируемые языки всё-таки классом выше (ниже?) чем интерпретируемые с точки зрения операционной системы. Потому что ELF это ELF, а скрипты — это просто текстовые файлы. Ни suid'а, ни адекватного использования в роли init/udev/etc.
С другой стороны, потеснить perl/python в качестве «bash++» не получится по той же причине — компилять надо.
С другой стороны, потеснить perl/python в качестве «bash++» не получится по той же причине — компилять надо.
Ну компилять первый раз можно и при запуске — не смертельно.
#!/usr/bin/gorun
package main
func main() {
println(«Hello world!»)
}
ufm@msi ~ $ time ./t.go
Hello world!
real 0m0.036s
user 0m0.023s
sys 0m0.015s
ufm@msi ~ $ time ./t.go
Hello world!
real 0m0.005s
user 0m0.000s
sys 0m0.005s
Для сравнения:
ufm@msi ~ $ time python -c 'print «Hello world!»'
Hello world!
real 0m0.008s
user 0m0.004s
sys 0m0.004s
ufm@msi ~ $ time php -r 'echo «Hello world!»;'
Hello world!
real 0m0.022s
user 0m0.015s
sys 0m0.011s
#!/usr/bin/gorun
package main
func main() {
println(«Hello world!»)
}
ufm@msi ~ $ time ./t.go
Hello world!
real 0m0.036s
user 0m0.023s
sys 0m0.015s
ufm@msi ~ $ time ./t.go
Hello world!
real 0m0.005s
user 0m0.000s
sys 0m0.005s
Для сравнения:
ufm@msi ~ $ time python -c 'print «Hello world!»'
Hello world!
real 0m0.008s
user 0m0.004s
sys 0m0.004s
ufm@msi ~ $ time php -r 'echo «Hello world!»;'
Hello world!
real 0m0.022s
user 0m0.015s
sys 0m0.011s
это было сделано намеренно, чтобы скопировал файл куда угодно и он работает, то есть когда на один сервер задеплоили 5 приложений у них не будет сломанных зависимостей. Хотя намечается тенденция сделать поддержку создания библиотек на go (в основном из-за android и там это как-то даже работает)
Ну и сами библиотеки (пакеты) go поставляются в виде исходников, поэтому плодить библиотеки (so, dll) не имеет смысла
Тажке компилятор go делает очень много inline оптимизаций, из-за этого один и тот же код может встречаться как в inline виде, так и обычном.
Ну и сами библиотеки (пакеты) go поставляются в виде исходников, поэтому плодить библиотеки (so, dll) не имеет смысла
Тажке компилятор go делает очень много inline оптимизаций, из-за этого один и тот же код может встречаться как в inline виде, так и обычном.
Попробуйте сжимать испольняемые бинари upx'ом.
А под *nix?
upx работает для разных архитектур linux и работает для «386-х» бинарников для маков и freebsdb:
upx.sourceforge.net/, таблица «Supported executable formats».
upx.sourceforge.net/, таблица «Supported executable formats».
мимо
Попробовал. Штатно не работает — ругается на неправильный размер файла.
Есть проект, который правит бинарник перед сжатием upx, но с подробным устройством бинаников я не знаком и неизвестно насколько такие правки переносимы и будет ли это дополнительным источником багов когда-то потом, когда я уже привыкну что всё работает.
Есть проект, который правит бинарник перед сжатием upx, но с подробным устройством бинаников я не знаком и неизвестно насколько такие правки переносимы и будет ли это дополнительным источником багов когда-то потом, когда я уже привыкну что всё работает.
У меня отлично сжимает бинарники Go программ и на OSX, и под Linux.
Что я делаю не так?
Обычно работаю с go 1.2.1, для проверки скачал последнюю версию. upx — из репозитория ubuntu 14.04
Обычно работаю с go 1.2.1, для проверки скачал последнюю версию. upx — из репозитория ubuntu 14.04
Заголовок спойлера
cd utils
GOROOT=/home/rekby/Downloads/go/ GOTOOLDIR=/home/rekby/Downloads/go/pkg/tool/linux_amd64/ /home/rekby/Downloads/go/bin/go build
upx utils
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2013
UPX 3.91 Markus Oberhumer, Laszlo Molnar & John Reiser Sep 30th 2013
File size Ratio Format Name
-------------------- ------ ----------- -----------
upx: utils: EOFException: premature end of file
Packed 1 file: 0 ok, 1 error.
bash-3.2$ cat server.go
package main
import (
"log"
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", func (rw http.ResponseWriter, req *http.Request) {
log.Printf("new request")
fmt.Fprintf(rw, "Hello %s", "World")
})
log.Printf("server starting")
http.ListenAndServe(":8080", nil)
}
bash-3.2$ go build -o server server.go
bash-3.2$ ls -l
total 11320
-rwxr-xr-x 1 miolini staff 5790996 Feb 24 15:00 server
-rw-r--r-- 1 miolini wheel 278 Feb 24 15:00 server.go
bash-3.2$ upx --lzma server
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2013
UPX 3.91 Markus Oberhumer, Laszlo Molnar & John Reiser Sep 30th 2013
File size Ratio Format Name
-------------------- ------ ----------- -----------
5790996 -> 1204224 20.79% Mach/AMD64 server
Packed 1 file.
bash-3.2$ ls -l
total 2360
-rwxr-xr-x 1 miolini staff 1204224 Feb 24 15:00 server
-rw-r--r-- 1 miolini wheel 278 Feb 24 15:00 server.go
Угу, или хранить только сжатые исходники, а компилировать прямо на RAM-диск, в /tmp каталог )))
Если уж стали делать «как в busybox» — то почему не использовали их вариант выбора команды до конца?
Иными словами, почему используется
Иными словами, почему используется
./any --multiex-command=test asdf
вместо ./any test asdf
?Это намеренное отступление — для унификации.
параметр --multiex-command включается глобально и работает с любым именем вызова, а не только с неизвестным.
Т.е. например если есть экспортированная функция test, которая принимает параметр run и собственно экспортированная функция run. Что должна выполнить команда ./test run?
параметр --multiex-command включается глобально и работает с любым именем вызова, а не только с неизвестным.
Т.е. например если есть экспортированная функция test, которая принимает параметр run и собственно экспортированная функция run. Что должна выполнить команда ./test run?
Если имя файла совпадает с именем функции — выполняться должна именно функция. Формат с указанием функции первым аргументом — только для исполнимого файла с «основным» именем.
PS представил себе:
Интересно, если бы кто-нибудь так и сделал, привело бы это к новым необычным уязвимостям?
PS представил себе:
cat --busybox-command=bash -c ...
Интересно, если бы кто-нибудь так и сделал, привело бы это к новым необычным уязвимостям?
Тут есть заметное отличие от busybox — busybox он всегда один, а вот такие multiex-ов может быть несколько, поэтому например он и свой модуль не инсталлирует по умолчанию и основного названия файла у него тоже нет — каждый разработчик может скомпилировать его по-своему и разные имена назначать кто символьными ссылками, кто жесткими. Как в этих условиях отличить главный вызов от неглавного (а просто с непредусмотренным именем) не понятно.
При необходимости такого же поведения первого параметра как у busybox можно написать модуль, вроде:
Тогда при ./asbb cat 123 будет вызвана программа cat 123.
Про уязвимости — согласен, с sudo могут быть проблемы. Тут не досмотрел (в моём контексте это не актуально). Возможно такой глобальный параметр стоит сделать только опционально включаемым.
При необходимости такого же поведения первого параметра как у busybox можно написать модуль, вроде:
func asbb(){
os.Args = os.Args[1:]
multiex.Main()
}
func main(){
multiex.Register(multiex.ExecutorDescribe{Name: "asbb", Function: asbb})
}
Тогда при ./asbb cat 123 будет вызвана программа cat 123.
Про уязвимости — согласен, с sudo могут быть проблемы. Тут не досмотрел (в моём контексте это не актуально). Возможно такой глобальный параметр стоит сделать только опционально включаемым.
подумал, решил что такой параметр для принудительного вызова команд действительно в некоторых сценариях (например sudo) может приводить к уязвимостям, кроме того это менее удобно чем просто первый аргумент, как в busybox.
Переделал.
Заодно еще и русский перевод везде добавил — посмотрим насколько это будет удобно для русскоязычных коллег.
Переделал.
Заодно еще и русский перевод везде добавил — посмотрим насколько это будет удобно для русскоязычных коллег.
Давно заметил, что Go-бинарники неплохо strip-аются, процентов на 20-25 худеют.
Go перемудрили со своим рантаймом. Почему бы не сделать как в том-же языке Crystal где бинарник линкуентся только лишь с системными библиотеками и не содержит никакого мусора. Как результат простой HTTP сервер на Кристале занимает десятки килобойт а на Go мегабайты.
Зачем Го хранить весть этот мусор в бинарниках?
Зачем Го хранить весть этот мусор в бинарниках?
Что вы подразумеваете под «мусором»?
Дизайн и решения каждого языка — это всегда компромиссы между десятками вещей — памятью и скоростью, тем, что разрешено программисту, а что отдано на откуп компилятору и так далее. Выбор в пользу одного или другого нюанса выбирает с точки зрения реалий, опыта и здравого смысла.
Если 20 лет еще было актуально экономить каждый кило(мега)байт дискового пространства, в ущерб простоте деплоя и кросскомпиляции, то сейчас мир изменился — лишние 5 мб на диске — это буквально ничто, зато профит от упрощения процедур деплоя и кросскомпиляции — колоссальный.
Те, кто в 2015-м требуют бинарников размеров в килобайты любой ценой — что-то важное упускают, имхо.
Дизайн и решения каждого языка — это всегда компромиссы между десятками вещей — памятью и скоростью, тем, что разрешено программисту, а что отдано на откуп компилятору и так далее. Выбор в пользу одного или другого нюанса выбирает с точки зрения реалий, опыта и здравого смысла.
Если 20 лет еще было актуально экономить каждый кило(мега)байт дискового пространства, в ущерб простоте деплоя и кросскомпиляции, то сейчас мир изменился — лишние 5 мб на диске — это буквально ничто, зато профит от упрощения процедур деплоя и кросскомпиляции — колоссальный.
Те, кто в 2015-м требуют бинарников размеров в килобайты любой ценой — что-то важное упускают, имхо.
Например чтобы один и тот же бинарник работал на любом линуксе, независимо от окружающих библиотек — это очень удобно.
1.5 уже умеет линковать динамически. Понимаю, что комментарий устарел, но мало ли)
а это вы как так собирали, что размер 9Кб получился?
я разные режимы сборки попробовал — везде около 1Мб минимум. Если использовать пакет net (например net/http) — бинарник действительно получается с динамической линковкой, но размер всё равно большой.
P.S. и заодно может знаете способ принудительно собирать статический бинарник даже при использовании сети? очень уж хорошо в прежних версиях иметь бинарник без зависимостей.
В моём случае нагрузки оч. маленькие и как раз простое получение бинарника без зависимостей, да еще и с простой кросс-компиляцией для меня одно из самых больших преимуществ go перед другими языками.
я разные режимы сборки попробовал — везде около 1Мб минимум. Если использовать пакет net (например net/http) — бинарник действительно получается с динамической линковкой, но размер всё равно большой.
P.S. и заодно может знаете способ принудительно собирать статический бинарник даже при использовании сети? очень уж хорошо в прежних версиях иметь бинарник без зависимостей.
В моём случае нагрузки оч. маленькие и как раз простое получение бинарника без зависимостей, да еще и с простой кросс-компиляцией для меня одно из самых больших преимуществ go перед другими языками.
Надо признать, что про динамическую линковку с net/http только узнал, посмотрел через ldd — действительно O_o.
По поводу того, как собирал:
Для примера, helloworld ниже после компиляции у меня занимает ~15KB, но сервер посложнее с mgo и gin может и мегабайт-два занимать. В теории, если эти библиотеки скомпилировать с -buildmode=shared, то тоже можно прилинковать динамически. На практике пока особо не вникал, в golang только неделю как пришел.
По поводу того, как собирал:
go install -buildmode=shared std
go build -linkshared hello.go
Для примера, helloworld ниже после компиляции у меня занимает ~15KB, но сервер посложнее с mgo и gin может и мегабайт-два занимать. В теории, если эти библиотеки скомпилировать с -buildmode=shared, то тоже можно прилинковать динамически. На практике пока особо не вникал, в golang только неделю как пришел.
package main
import (
"io"
"log"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, string("Hello world"))
})
log.Print("Server started at http://localhost:8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
да, тоже получилось — я
не делал до этого.
Но есть но:
1. При распространении придется таскать за собой скомпилированный libstd.so, а это в данный момент 37МБ.
2. Снова возвращаемся к зависимостям что для этого бинарника нужна одна версия libstd, для другого — другая и т.п.
Так что меня даже больше интересует какой-то способ принудительно собирать статический бинарник даже при работе с сетью. Чтобы dns-запросы как раньше работали «медленно и неэффективно», в отдельном процессе (не горутине) и т.п., но без внешних зависимостей.
go install -buildmode=shared std
не делал до этого.
Но есть но:
1. При распространении придется таскать за собой скомпилированный libstd.so, а это в данный момент 37МБ.
2. Снова возвращаемся к зависимостям что для этого бинарника нужна одна версия libstd, для другого — другая и т.п.
Так что меня даже больше интересует какой-то способ принудительно собирать статический бинарник даже при работе с сетью. Чтобы dns-запросы как раньше работали «медленно и неэффективно», в отдельном процессе (не горутине) и т.п., но без внешних зависимостей.
Ну здесь уже палка о двух концах. К примеру на сервере, где крутится сотня-другая микросервисов — имеет смысл вынести libstd. Ну и если go все же получит распространение, и в репозитариях операционок будет достаточное количество приложений с зависимостью от пакета libstd, то он установится только с первой программой.
По поводу второго вопроса, попробуйте сделать так (нагуглил):
По поводу второго вопроса, попробуйте сделать так (нагуглил):
go build -ldflags "-linkmode external -extldflags -static" hello.go
К сожалению не знаком с windows
Значит вам ближе будет эта ссылка: ru.wikipedia.org/wiki/Dependency_hell
Там же и решение приведено: «Хороший пакетный менеджер».
Сферический и в вакууме :-)
А вот этот кусок мне особенно нравится:
Although these repositories are often huge it is not possible to have every piece of software in them, so dependency hell can still occur. In all cases, dependency hell is still faced by the repository maintainers.
А вот этот кусок мне особенно нравится:
В таком случае удовлетворение долгой цепи зависимостей пакетов даже может привести, например, к запросу другой версии библиотеки glibc, одной из крайне важных, основополагающих системных библиотек. Если это случается пользователю будет предложено удалить тысячи пакетов, что по сути будет равноценно удалению, например, 80% системы, включая графические оболочки и сотни различных программ.
По поводу сборки — отлично, как раз то, что нужно.
По поводу stdlib я как раз про то же что и vintage парой коментов выше.
Для каждого бинарника с динамической линковкой потребуется stdlib, собранный из той же версии исходников что и бинарник. Это не зависит от операционки — в Linux будет так же.
Т.е. например включат в дистрибутив stdlib от go 1.5, потом кто-то что-то напишет кто-то что-то на 1.5.1 и нужен уже новый stdlib, т.к. там код поменялся (ошибки поправлены), то же с 1.5.2, потом 1.6 — там уже и реализация runtime скорее всего поменяется и т.д.
Т.е. рассчитывать на системную библиотеку не получится и runtime придётся всё равно надо таскать с собой.
т.е. это может иметь смысл в случае как я описывал — когда сотня мелких утилит по три строчки в каждой, они все вместе компилируются и потом копируются на целевые серверы и тогда с ними можно в нагрузку дать stdlib, правда придется еще учитывать настройки окружения — чтобы подцеплялся именно этот stdlib.so, а не какой-то еще.
Для каждого бинарника с динамической линковкой потребуется stdlib, собранный из той же версии исходников что и бинарник. Это не зависит от операционки — в Linux будет так же.
Т.е. например включат в дистрибутив stdlib от go 1.5, потом кто-то что-то напишет кто-то что-то на 1.5.1 и нужен уже новый stdlib, т.к. там код поменялся (ошибки поправлены), то же с 1.5.2, потом 1.6 — там уже и реализация runtime скорее всего поменяется и т.д.
Т.е. рассчитывать на системную библиотеку не получится и runtime придётся всё равно надо таскать с собой.
т.е. это может иметь смысл в случае как я описывал — когда сотня мелких утилит по три строчки в каждой, они все вместе компилируются и потом копируются на целевые серверы и тогда с ними можно в нагрузку дать stdlib, правда придется еще учитывать настройки окружения — чтобы подцеплялся именно этот stdlib.so, а не какой-то еще.
Версии библиотек неплохо разруливаются пакетными менеджерами ОС. Если библиотеки обратно совместимы, то достаточно их переодически обновлять. Мне кажется проблема надумана.
Если же компилировать микросервисы в один экзешник, то теряются многие плюсы такой структуры. Например, обновления (или, не дай бог, ошибки) одного микросервиса, будут затрагивать все остальные, чего очень хотелось бы, особенно с проектами в активной фазе разработки.
Если же компилировать микросервисы в один экзешник, то теряются многие плюсы такой структуры. Например, обновления (или, не дай бог, ошибки) одного микросервиса, будут затрагивать все остальные, чего очень хотелось бы, особенно с проектами в активной фазе разработки.
Насколько я понял docs.google.com/document/d/1nr-TQHw_er6GOQRsF6T43GGhFDelrAP0NqSS_00RgZQ/edit?pli=1 раздел Multiples copies of a Go package — пока что обратной совместимости нет. Она может появиться когда-то потом, но без конкретных планов.
Об этом я думал, но не всё так плохо:
1. Работают микросервисы независимо друг от друга, хоть и находятся в одном бинарнике — кодовая база у них раздельна, зависимостей нет. Просто они разделяют общие внешние зависимости. Т.е. если в коде одного из них есть ошибка и он будет падать — на других микросервисах это не отразится, т.к. будут работать другие копии процесса, где ошибочный код не выполняется, т.к. работает другой микросервис/команда.
При запуске каждой внешней команды запускается только одна команда/сервис. С другими она не пересекается. Единственное исключение — код из init-функций выполняется до вызова main в любом случае из всех модулей.
2. Есть проблема что при обновлении бинарника обновляется сразу всё что в него скомпилировано. Но это вполне решается на уровне сборки — т.е. можно разрабатывать микросервисы/команды отдельно, а в общем сборщике для публикации внешним инструментом фиксировать версии, которые туда попадают, независимо от того на каких этапах разработка происходит. Это например легко решается через системы контроля версий: submodules в git и externals на конкретную ревизию в SVN. Обновлять можно так же по одному сервису за раз — как и обычная публикация обычно происходит.
Тогда бинарник включает в себя нужные версии микросервисов — собственно когда мы просто копируем этот набор команд/микросервисов на целевую систему (в бинарниках) — там тоже получается свой набор.
Просто тут он фиксируется на этапе сборки, а не на этапе публикации.
Если же компилировать микросервисы в один экзешник, то теряются многие плюсы такой структуры. Например, обновления (или, не дай бог, ошибки) одного микросервиса, будут затрагивать все остальные, чего очень хотелось бы, особенно с проектами в активной фазе разработки.
Об этом я думал, но не всё так плохо:
1. Работают микросервисы независимо друг от друга, хоть и находятся в одном бинарнике — кодовая база у них раздельна, зависимостей нет. Просто они разделяют общие внешние зависимости. Т.е. если в коде одного из них есть ошибка и он будет падать — на других микросервисах это не отразится, т.к. будут работать другие копии процесса, где ошибочный код не выполняется, т.к. работает другой микросервис/команда.
При запуске каждой внешней команды запускается только одна команда/сервис. С другими она не пересекается. Единственное исключение — код из init-функций выполняется до вызова main в любом случае из всех модулей.
2. Есть проблема что при обновлении бинарника обновляется сразу всё что в него скомпилировано. Но это вполне решается на уровне сборки — т.е. можно разрабатывать микросервисы/команды отдельно, а в общем сборщике для публикации внешним инструментом фиксировать версии, которые туда попадают, независимо от того на каких этапах разработка происходит. Это например легко решается через системы контроля версий: submodules в git и externals на конкретную ревизию в SVN. Обновлять можно так же по одному сервису за раз — как и обычная публикация обычно происходит.
Тогда бинарник включает в себя нужные версии микросервисов — собственно когда мы просто копируем этот набор команд/микросервисов на целевую систему (в бинарниках) — там тоже получается свой набор.
Просто тут он фиксируется на этапе сборки, а не на этапе публикации.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Уменьшение накладных расходов для утилит на golang