как получить наименьший скомпилированный нативный бинарный файл ocamlopt? - PullRequest
2 голосов
/ 20 сентября 2019

Я был очень удивлен, увидев, что даже простая программа, такая как:

print_string "Hello world !\n";

, когда статически компилируется в нативный код через ocamlopt с некоторыми довольно агрессивными опциями (используя musl), все равно будетоколо ~ 190 КБ в моей системе.

$ ocamlopt.opt -compact -verbose -o helloworld \
    -ccopt -static \
    -ccopt -s \
    -ccopt -ffunction-sections \
    -ccopt -fdata-sections \
    -ccopt -Wl \
    -ccopt -gc-sections \
    -ccopt -fno-stack-protector \
    helloworld.ml && { ./helloworld ; du -h helloworld; }
+ as -o 'helloworld.o' '/tmp/camlasm759655.s'
+ as -o '/tmp/camlstartupfc4271.o' '/tmp/camlstartup5a7610.s'
+ musl-gcc -Os -o 'helloworld'   '-L/home/vaab/.opam/4.02.3+musl+static/lib/ocaml' -static -s -ffunction-sections -fdata-sections -Wl -gc-sections -fno-stack-protector '/tmp/camlstartupfc4271.o' '/home/vaab/.opam/4.02.3+musl+static/lib/ocaml/std_exit.o' 'helloworld.o' '/home/vaab/.opam/4.02.3+musl+static/lib/ocaml/stdlib.a' '/home/vaab/.opam/4.02.3+musl+static/lib/ocaml/libasmrun.a' -static  -lm 
Hello world !
196K    helloworld

Как получить наименьший двоичный файл из ocamlopt?

Размер 190KB - это слишком много для простогопрограмма, подобная этой в сегодняшних ограничениях (iot, android, alpine VM ...), и плохо сравнивается с простой программой на C (около 6 КБ, или непосредственно кодируя ASM и настраивая вещи, чтобы получить рабочий двоичный файл, который может быть около 150 B).Я наивно думал, что могу просто отказаться от C, чтобы написать простую статическую программу, которая будет выполнять тривиальные задачи, и после компиляции я получу некоторый простой ассемблерный код, размер которого не будет настолько большим по размеру с эквивалентной программой на Си.Это возможно ?

То, что я понимаю, я понимаю:

При удалении gcc -s, чтобы получить некоторые подсказки о том, что осталось в двоичном файле, я могу заметить много ocaml символы, и я также вроде как читал, что некоторая переменная окружения ocamlrun предназначена для интерпретации даже в этой форме .Это как если бы то, что ocamlopt называет «нативной компиляцией», означает упаковку ocamlrun и не-нативной bytecode вашей программы в один файл и делает ее исполняемой.Не совсем то, что я ожидал.Я явно пропустил какой-то важный момент.Но если это так, мне будет интересно, почему это не так, как я ожидал.

Другие языки, компилирующие в нативный код, имеют ту же проблему: оставляя некоторому наивному пользователю (как и мне) примерно одинаковымвопросы:

Я также тестировал с Haskell, и без твиков все компиляторы языков делают бинарные файлы размером более 700 КБ для программы "hello world" (до Ocaml это было то же самое).

1 Ответ

5 голосов
/ 20 сентября 2019

Ваш вопрос очень широкий, и я не уверен, что он соответствует формату Stackoverflow.Это заслуживает тщательного обсуждения .

Размер 190 КБ - это слишком много для простой программы, такой как в современных ограничениях (iot, android, alpine VM ...), и плохо сравнивается с простой программой на C (около ~ 6 КБ,или непосредственно кодируя ASM и настраивая вещи, чтобы получить работающий двоичный файл, который может быть около 150B)

Прежде всего, это не честное сравнение.В настоящее время скомпилированный двоичный файл C является артефактом, который далек от того, чтобы быть автономным двоичным файлом.Это должно быть больше похоже на плагин в рамках.Поэтому, если вы хотите подсчитать, сколько байтов фактически использует данный двоичный файл, мы посчитаем размер загрузчика, оболочки, библиотеки libc и всего ядра Linux или Windows - которые в совокупности образуют среду выполнения приложения.

OCaml, в отличие от Java или Common Lisp, очень дружественен к обычной среде выполнения C и пытается использовать большинство своих возможностей.Но OCaml все еще имеет собственную среду выполнения, в которой самая большая (и самая важная часть) - сборщик мусора.Время выполнения не очень большое (около 30 KLOC), но все же способствует увеличению веса.А поскольку OCaml использует статическое связывание, каждая программа OCaml будет иметь его копию.

Следовательно, двоичные файлы C имеют существенное преимущество, поскольку они обычно запускаются в системах, где среда выполнения C уже доступна (поэтому она обычно исключается из уравнения).Однако существуют системы, в которых время выполнения C вообще отсутствует, и присутствует только время выполнения OCaml, см., Например, Mirage .В таких системах двоичные файлы OCaml гораздо более выгодны.Другим примером является проект OCaPic , в котором (после настройки компилятора и среды выполнения) им удалось приспособить среду выполнения OCaml и программы к 64 КБ Flash (см. Статью , очень проницательно одвоичные размеры).

Как получить наименьший двоичный файл из ocamlopt?

Когда действительно необходимо минимизировать размер, используйте Mirage Unikernels или реализуйте свою собственную среду выполнения.Для общих случаев используйте strip и upx.(Например, с upx --best я смог уменьшить двоичный размер вашего примера до 50К, без каких-либо дополнительных хитростей).Если производительность не имеет большого значения, вы можете использовать байт-код, который обычно меньше машинного кода.Таким образом, вы заплатите один раз (около 200 тыс. За время выполнения) и несколько байтов для каждой программы (например, 200 байт для вашего helloworld).

Кроме того, не создавайте много маленьких двоичных файлов, но создайте один двоичный файл.В вашем конкретном примере размер модуля компиляции helloworld составляет 200 байтов в байт-коде и 700 байтов в машинном коде.Остальные 50 КБ - это стартовый жгут, который должен быть включен только один раз.Более того, поскольку OCaml поддерживает динамическое связывание во время выполнения, вы можете легко создать загрузчик, который будет загружать модули при необходимости.И в этом случае двоичные файлы станут очень маленькими (сотни байтов).

Как будто то, что ocamlopt называет «нативной компиляцией», касается упаковки ocamlrun и не родного байт-кода вашей программы.в одном файле и сделать его исполняемым.Не совсем то, что я ожидал.Я явно пропустил какой-то важный момент.Но если это так, мне будет интересно, почему это не так, как я ожидал.

Нет-нет, это совершенно неправильно.Собственная компиляция - это когда программа компилируется в машинный код, будь то x86, ARM или что-то еще.Среда выполнения написана на C, скомпилирована в машинный код и также связана.Стандартная библиотека OCaml написана в основном на OCaml, также скомпилирована в машинный код и также связана с двоичным файлом (только те модули, которые используются, статическое связывание OCaml очень эффективно при условии, что программа разбита на модули (модули компиляции)довольно хорошо).

Что касается переменной среды OCAMLRUNPARAM, то это просто переменная среды, которая параметризует поведение среды выполнения, в основном это параметры сборщика мусора.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...