compilation - 如何获得最小的 ocamlopt 编译的 native 二进制文件?

标签 compilation size ocaml executable minimum

我很惊讶地发现,即使是一个简单的程序,例如:

print_string "Hello world !\n";

当通过 ocamlopt 使用一些相当激进的选项(使用 musl)静态编译为 native 代码时,在我的系统上仍约为 190KB 左右。

$ 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 的大小对于像当今的限制(物联网、android、alpine VM...)这样的简单程序来说太大了,并且与简单的 C 程序(大约 6KB)相比效果不佳,或者直接编码 ASM 并进行调整以获得可能大约 150B 的工作二进制文件)。我天真地认为我可以简单地放弃 C 来编写简单的静态程序来完成一些琐碎的事情,编译后我会得到一些简单的汇编代码,这些代码的大小与等效的 C 程序相比不会那么大。那可能吗 ?

我认为我理解的内容:

当删除 gcc 的 -s 以获取有关二进制文件中剩余内容的一些提示时,我可以注意到很多 ocaml 符号,而且我还读到了一些ocamlrun 的环境变量 are meant to be interpreted even in this form 。就好像 ocamlopt 所谓的“ native 编译”是将程序的 ocamlrun 和非 native 字节码 打包到一个文件中,然后进行编译它可执行。不完全是我所期望的。我显然错过了一些重要的事情。但如果是这样的话,我会感兴趣为什么它不像我预期的那样。

编译为 native 代码的其他语言也有同样的问题:让一些天真的用户(如我自己)遇到大致相同的问题:

我还使用 Haskell 进行了测试,在不进行调整的情况下,所有语言编译器都会为“hello world”程序生成超过 700KB 的二进制文件(调整之前的 Ocaml 也是如此)。

最佳答案

您的问题非常广泛,我不确定它是否符合 Stackoverflow 的格式。它值得彻底discussion

A size of 190KB is way too much for a simple program like that in today's constraints (iot, android, alpine VM...), and compares badly with simple C program (around ~6KB, or directly coding ASM and tweaking things to get a working binary that could be around 150B)

首先,这不是一个公平的比较。如今,已编译的 C 二进制文件是一个工件,远非独立的二进制文件。它应该更像是框架中的插件。因此,如果您想计算给定的二进制文件实际使用了多少字节,我们将计算加载程序、shell、libc 库以及整个 Linux 或 Windows 内核的大小 - 它们总共构成了应用程序的运行时。

与 Java 或 Common Lisp 不同,OCaml 对通用 C 运行时非常友好,并尝试重用其大部分功能。但 OCaml 仍然带有自己的运行时,其中最大(也是最重要的部分)是垃圾收集器。运行时间并不是很大(大约 30 KLOC),但仍然增加了重量。由于 OCaml 使用静态链接,每个 OCaml 程序都会有一个它的副本。

因此,C 二进制文件具有显着的优势,因为它们通常在 C 运行时已经可用的系统中运行(因此通常将其排​​除在等式之外)。然而,有些系统根本没有 C 运行时,只有 OCaml 运行时,请参阅 Mirage例如。在此类系统中,OCaml 二进制文件更为有利。另一个例子是OCaPic项目中(在调整编译器和运行时之后),他们设法将 OCaml 运行时和程序装入 64Kb Flash(阅读paper,它对二进制大小非常有洞察力)。

How to get the smallest binary from ocamlopt?

当确实需要最小化大小时,请使用 Mirage Unikernels 或实现您自己的运行时。对于一般情况,请使用 stripupx。 (例如,使用 upx --best 我能够将示例的二进制大小减少到 50K,无需任何其他技巧)。如果性能不太重要,那么您可以使用字节码,它通常比机器代码小。因此,您只需支付一次费用(运行时费用约为 200k),并且为每个程序支付几个字节(例如,您的 helloworld 为 200 字节)。

此外,不要创建许多小的二进制文件,而是创建一个二进制文件。在您的特定示例中,helloworld 编译单元的大小在字节码中为 200 字节,在机器代码中为 700 字节。剩下的 50k 是启动线束,应该只包含一次。此外,由于 OCaml 支持运行时动态链接,因此您可以轻松创建一个加载器,在需要时加载模块。在这种情况下,二进制文件将变得非常小(数百字节)。

It is as if what ocamlopt calls "native compilation" is about packing ocamlrun and the not-native bytecode of your program in one file and make it executable. Not exactly what I would have expected. I obviously missed some important point. But if that is the case, I'll be interested why it isn't as I expected.

不不,这是完全错误的。 native 编译是指将程序编译为机器代码,无论是 x86、ARM 还是其他。运行时是用 C 编写的,编译为机器代码,并且也是链接的。 OCaml 标准库主要是用 OCaml 编写的,也编译为机器代码,也链接为二进制文件(仅使用那些模块,OCaml 静态链接非常高效,前提是将程序拆分为模块(编译单元)相当好)。

关于OCAMLRUNPARAM环境变量,它只是一个参数化运行时行为的环境变量,主要是垃圾收集器的参数。

关于compilation - 如何获得最小的 ocamlopt 编译的 native 二进制文件?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58028824/

相关文章:

python - 从 Python 代码构建 .NET DLL?

Python 3.2 - uWSGI 进程出现段错误

c++ - 如何在linux下基于caffe编译cpp

C 中的编译错误 : Inconsistent type declaration/Illegal redeclaration for identifier

html - 如何在浏览器窗口较小的情况下均匀间隔三张图片,并避免它们重叠?

node.js - 使用 Knex.js 增加 Oracle DB 表中的列大小

OCaml:查找特定类型的值

recursion - 如何记住递归函数?

file - 如何找到某些指定文件的大小?

ocaml - OCaml 的类型系统是否会阻止它对 Church 数字进行建模?